MongoDB $skip Stage

Beginner
⏱️ 10 min read
📚 Updated: Jul 2026
🎯 5 Examples
Aggregation

What You’ll Learn

The $skip stage offsets the pipeline stream by discarding the first N documents—essential for page 2, page 3, and any API that returns results in fixed-size chunks after sorting.

01

Offset N

Drop first rows.

02

Pagination

With $limit.

03

$sort first

Stable pages.

04

Page formula

(page-1)×size.

05

Performance

Large skip cost.

06

vs $limit

Offset vs cap.

Definition and Usage

$skip is an aggregation stage that removes the first N documents from the current pipeline stream. Whatever remains flows to the next stage. It does not sort, filter, or reshape documents—it only shifts the window forward.

💡
Beginner tip

$limit: 5 = “keep at most 5 documents.”
$skip: 5 = “ignore the first 5, keep the rest.”
Page 2 with 5 per page: $skip: 5 then $limit: 5—after $sort.

Pagination pattern: $match$sort$skip$limit. The sort guarantees page 1 and page 2 show different but predictable slices. Without $sort, skipped documents may appear in arbitrary order.

📝 Syntax

$skip takes a single non-negative integer—the number of documents to discard:

mongosh
{ $skip: <non-negative integer N> }

Syntax Rules

  • Integer ≥ 00 skips nothing; 10 drops the first 10 documents.
  • Order-dependent — skips from the current pipeline order; use $sort first for meaningful pagination.
  • Pair with $limit — typical page size cap comes immediately after $skip.
  • Fewer than N docs — if input has fewer than N documents, output is empty.
  • Not a filter — use $match to exclude documents by criteria; $skip only counts position.
  • Performance — large N forces MongoDB to scan and discard N documents every query.
mongosh
db.products.aggregate([
  { $sort: { price: 1 } },
  { $skip: 3 },
  { $limit: 2 }
])

⚡ Quick Reference

QuestionAnswer
What it doesDrops first N documents from pipeline stream
Syntax{ $skip: N } where N ≥ 0
Pagination formulaskip = (page - 1) × pageSize
Recommended order$sort$skip$limit
vs $limit$skip offset; $limit caps count
Deep pagesHigh skip is slow—prefer cursor pagination
Page 1
{ $skip: 0 }
{ $limit: 10 }

First 10 rows

Page 2
{ $skip: 10 }
{ $limit: 10 }

Rows 11–20

Page 3
{ $skip: 20 }
{ $limit: 10 }

Rows 21–30

Full pattern
$sort →
$skip →
$limit

Stable pagination

🧰 $skip Value & Pipeline Placement

$skip has one argument, but where you place it in the pipeline changes behavior:

N = 0 No-op

Valid and common for page 1 APIs—keeps pipeline structure identical across pages while only changing the skip variable.

{ $skip: 0 }
{ $limit: 20 }
After $sort Pattern

Sort defines order; skip selects the offset into that ordered list—required for consistent user-facing pagination.

{ $sort: { createdAt: -1 } },
{ $skip: 40 },
{ $limit: 20 }
After $match Filter first

Filter the collection before sort/skip/limit so pagination runs on the matching subset—not the entire collection.

{ $match: { status: "active" } },
{ $sort: { name: 1 } },
{ $skip: 5 }
Large N Caution

$skip: 100000 reads and discards 100k docs each request. Use range queries on sort keys for deep pagination instead.

// Instead of skip 100000:
{ $match: { _id: { $gt: lastId } } },
{ $limit: 20 }

Examples Gallery

Products collection—basic offset, paginated API pages, dynamic skip variables, filtered pagination, and why deep skip hurts performance.

📚 Getting Started

Ten products sorted by price for skip labs.

Example 1 — Products collection

mongosh
db.products.drop()

db.products.insertMany([
  { sku: "P01", name: "Cable",    category: "accessories", price: 9,  stock: 120 },
  { sku: "P02", name: "Mouse",    category: "accessories", price: 19, stock: 80  },
  { sku: "P03", name: "Keyboard", category: "accessories", price: 49, stock: 45  },
  { sku: "P04", name: "Webcam",   category: "accessories", price: 59, stock: 30  },
  { sku: "P05", name: "Monitor",  category: "displays",    price: 199, stock: 15 },
  { sku: "P06", name: "Headset",  category: "audio",       price: 79, stock: 55  },
  { sku: "P07", name: "Speaker",  category: "audio",       price: 129, stock: 25  },
  { sku: "P08", name: "Tablet",   category: "devices",     price: 299, stock: 12  },
  { sku: "P09", name: "Laptop",   category: "devices",     price: 899, stock: 8   },
  { sku: "P10", name: "Dock",     category: "accessories", price: 149, stock: 20  }
])

How It Works

Ten products with varied prices give clear pagination slices—when sorted by price ascending, skip 3 drops Cable, Mouse, and Keyboard.

Example 2 — Skip first 3 products (basic $skip)

mongosh
db.products.aggregate([
  { $sort: { price: 1 } },
  { $skip: 3 },
  {
    $project: {
      _id: 0,
      sku: 1,
      name: 1,
      price: 1
    }
  }
])

// Sorted by price ascending:
// Skip P01 Cable ($9), P02 Mouse ($19), P03 Keyboard ($49)
// Output starts at P04 Webcam ($59)
// 7 documents returned (products 4–10)

How It Works

Always sort before skip when the offset must mean something specific—“skip cheapest 3” requires price sort first.

Example 3 — Page 2 with $sort + $skip + $limit

mongosh
// Page size = 4, request page 2
const page = 2
const pageSize = 4
const skip = (page - 1) * pageSize  // (2-1)*4 = 4

db.products.aggregate([
  { $sort: { price: 1 } },
  { $skip: skip },
  { $limit: pageSize },
  {
    $project: {
      _id: 0,
      sku: 1,
      name: 1,
      price: 1
    }
  }
])

// Page 1 (skip 0):  P01–P04  ($9–$59)
// Page 2 (skip 4):  P06–P09  ($79–$299)
//   Headset, Speaker, Dock, Tablet
// Page 3 (skip 8):  P09–P10  (last 2 items)

How It Works

This is the standard REST pagination pattern: compute skip from page number, sort for stable order, skip to offset, limit to page size.

📈 Practical Patterns

Filtered pagination and avoiding expensive deep skips.

Example 4 — Filtered pagination (accessories only, page 1)

mongosh
const page = 1
const pageSize = 3
const skip = (page - 1) * pageSize

db.products.aggregate([
  { $match: { category: "accessories" } },
  { $sort: { price: 1 } },
  { $skip: skip },
  { $limit: pageSize },
  {
    $project: {
      _id: 0,
      name: 1,
      category: 1,
      price: 1
    }
  }
])

// Pool: Cable, Mouse, Keyboard, Webcam, Dock (5 accessories)
// Page 1 skip 0 limit 3 → Cable, Mouse, Keyboard
// Page 2 skip 3 limit 3 → Webcam, Dock (only 2 left)

How It Works

Real APIs paginate within a filtered set—active products, one category, or search results. Filter with $match before sort/skip/limit so page numbers refer to the filtered list.

Example 5 — Deep skip vs cursor-style pagination

mongosh
// SLOW on large collections — skip 500000 rows
db.products.aggregate([
  { $sort: { price: 1 } },
  { $skip: 500000 },
  { $limit: 20 }
])
// MongoDB must process 500020 documents every call

// FASTER pattern — cursor pagination by sort key
// Client sends lastPrice from previous page
const lastPrice = 149
const lastSku = "P10"

db.products.aggregate([
  {
    $match: {
      $or: [
        { price: { $gt: lastPrice } },
        { price: lastPrice, sku: { $gt: lastSku } }
      ]
    }
  },
  { $sort: { price: 1, sku: 1 } },
  { $limit: 20 },
  {
    $project: {
      _id: 0,
      sku: 1,
      name: 1,
      price: 1
    }
  }
])

// Uses index on price+sku — no massive skip scan
// Ideal for infinite scroll and deep page numbers

How It Works

$skip is fine for page 1–20 in most apps. For page 5000, cursor pagination (filter where sort key > last seen) scales far better than multiplying page number by page size.

🧠 How $skip Works

1

Ordered stream

Documents arrive from prior stages—ideally already sorted and filtered.

Input
2

First N discarded

$skip walks the stream and drops the first N documents without passing them downstream.

Skip
3

Remainder continues

Document N+1 onward flow to $limit, $project, or the client.

Pass
=

Offset window

Combined with $limit, you get a fixed-size page starting at the chosen offset.

📝 Notes

  • $skip accepts a non-negative integer; 0 is valid.
  • Always $sort before $skip when page order must be deterministic.
  • Formula: skip = (page - 1) × pageSize.
  • If fewer than N documents exist, $skip: N returns an empty result set.
  • Large skip values are expensive—each query re-scans skipped documents.
  • Previous topic: $set. Next: $sort.

Conclusion

$skip offsets the pipeline stream: pair it with $sort and $limit for pagination, compute skip from page number, and avoid huge skip values on large collections.

Next in the series: $sort—the stage that defines order before you skip and limit.

💡 Best Practices

✅ Do

  • Use $sort$skip$limit for paginated APIs
  • Filter with $match before paginating a subset
  • Index fields used in $sort when possible
  • Use $skip: 0 on page 1 for consistent pipeline templates
  • Switch to cursor pagination for deep page numbers

❌ Don’t

  • Skip without sorting when order matters to users
  • Use very large $skip on million-document collections
  • Confuse $skip with $limit—they solve different problems
  • Assume skipped documents are filtered—they are only bypassed
  • Put $skip before $sort when building ranked pages

Key Takeaways

Knowledge Unlocked

Five things to remember about $skip

Use these points whenever you paginate aggregation results.

5
Core concepts
📄 02

+ $limit

Page size.

Pagination
📈 03

Sort first

Stable order.

Order
🔢 04

Page formula

(p-1)×size.

Math
05

Deep pages

Use cursors.

Perf

❓ Frequently Asked Questions

$skip discards the first N documents from the pipeline stream and passes the rest to the next stage. Use it to offset results—for example, skip the first 20 rows to show page 2 of a paginated list.
{ $skip: N } where N is a non-negative integer. Example: { $skip: 10 } drops the first 10 documents. { $skip: 0 } is valid and skips nothing.
Always $sort first for consistent order, then $skip, then $limit. Page 1 (size 5): { $skip: 0 }, { $limit: 5 }. Page 2: { $skip: 5 }, { $limit: 5 }. Formula: skip = (page - 1) × pageSize.
Yes. $skip applies to documents in their current pipeline order. Put $sort before $skip when pagination must be deterministic. Put $match before $sort to filter first, then sort, then skip/limit.
Yes. MongoDB must walk past every skipped document—skipping 100,000 rows still reads and discards 100,000 documents. For deep pagination, prefer cursor-based pagination (filter by _id or sort key from last page) instead of ever-growing $skip values.
$skip removes documents from the start of the stream (offset). $limit caps how many documents continue (page size). Together they implement offset pagination; neither changes sort order—you need $sort for that.
Did you know?

Offset pagination with $skip is the same idea as SQL OFFSET—and shares the same weakness at high offsets. MongoDB must still walk past every skipped document, which is why production apps often switch to keyset (cursor) pagination for page 100+. See the official $skip docs.

Continue the Stages Series

Offset with $skip, then order with $sort.

Next: $sort →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful