MongoDB
aggregation framework
skip and limit
database management
data processing

skip and limit in aggregation framework

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

In MongoDB aggregation, "$skip" and "$limit" control how many documents continue through the pipeline. They are central to pagination and resource control, but performance depends heavily on stage order and sort strategy. Used carefully, they keep responses predictable; used blindly, they can create expensive scans on large collections.

What "$skip" and "$limit" Actually Do

"$skip" discards the first N documents from the current pipeline stream. "$limit" keeps only the first N documents and drops the rest.

Basic examples:

javascript
db.orders.aggregate([
  { $skip: 10 }
]);
javascript
db.orders.aggregate([
  { $limit: 5 }
]);

These stages act on the order established by previous stages. Without explicit "$sort", result order may be inconsistent across executions.

Correct Pagination Pattern

For page-based APIs, typical order is:

  1. "$match" for filtering.
  2. "$sort" for deterministic order.
  3. "$skip" for offset.
  4. "$limit" for page size.
javascript
1const page = 3;
2const pageSize = 20;
3const offset = (page - 1) * pageSize;
4
5db.orders.aggregate([
6  { $match: { status: "shipped" } },
7  { $sort: { orderDate: -1, _id: -1 } },
8  { $skip: offset },
9  { $limit: pageSize }
10]);

Sorting by a stable key pair such as orderDate plus _id avoids duplicate or missing rows when timestamps tie.

Performance Implications

Large skip values can be expensive because MongoDB still traverses skipped documents. Even with indexes, high offsets may become slow for deep pages.

To improve:

  • index fields used in "$match" and "$sort".
  • keep page size bounded.
  • consider keyset pagination for very deep navigation.

Keyset style avoids large offsets by using last-seen values:

javascript
1db.orders.aggregate([
2  { $match: { status: "shipped", orderDate: { $lt: ISODate("2025-07-01T00:00:00Z") } } },
3  { $sort: { orderDate: -1, _id: -1 } },
4  { $limit: 20 }
5]);

This is often faster and more stable for high-volume feeds.

Stage Ordering Mistakes to Avoid

Placing "$skip" before a narrowing "$match" often increases work. Likewise, sorting after skip can produce incorrect pagination semantics.

Bad pattern:

javascript
1db.orders.aggregate([
2  { $skip: 5000 },
3  { $match: { status: "shipped" } },
4  { $limit: 20 }
5]);

Here MongoDB discards 5000 documents before applying status filter, which wastes effort and returns unexpected pages.

Practical API Design Notes

When exposing pagination in APIs:

  • cap maximum limit.
  • validate non-negative skip.
  • return metadata such as page size and next cursor.
  • include deterministic sort keys in every paged query.

For analytics endpoints, consider separate count queries or a "$facet" pipeline when you need both data and total count in one call.

javascript
1db.orders.aggregate([
2  { $match: { status: "shipped" } },
3  {
4    $facet: {
5      data: [
6        { $sort: { orderDate: -1, _id: -1 } },
7        { $skip: 40 },
8        { $limit: 20 }
9      ],
10      meta: [
11        { $count: "total" }
12      ]
13    }
14  }
15]);

Common Pitfalls

  • Using "$skip" without deterministic "$sort".
  • Deep offset pagination on large collections causing slow responses.
  • Applying "$skip" before selective "$match" and increasing scan cost.
  • Allowing unbounded "$limit" values in public APIs.
  • Assuming skip-limit pagination is always correct under rapidly changing data.

Summary

  • "$skip" discards leading documents and "$limit" caps output size.
  • Always pair pagination with explicit deterministic sorting.
  • Stage order strongly affects both correctness and performance.
  • Large offsets are costly, so consider keyset pagination for deep pages.
  • Validate pagination inputs and enforce safe API limits. Monitor slow query logs after pagination changes to confirm real production impact.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.