$skip is a MongoDB aggregation stage that discards the first N documents from the current pipeline and passes the rest through—the offset half of offset pagination.
Remember
$skip = "drop the first N, keep the rest"
{ $skip: 5 }
does NOT sort by itself
for pages → $sort → $skip → $limit
skip = (page - 1) × pageSize
{ $skip: 0 } → skip nothing
large N → expensive (walks past every skipped doc)
find().skip(N) → similar idea outside aggregation
We use a small articles collection. First skip a few after sorting, then build a full page with $limit. Run these in mongosh after inserting the sample data once.
Example 1 — Sample Data
Eight articles with views and a published flag—enough for skip and page demos.
Intro to MongoDB 12400 published
Aggregation Basics 8900 published
Node.js + MongoDB 15200 published
Indexing Strategies 6700 published
Draft: Sharding 0 draft
REST API Design 11300 published
Schema Validation 5400 published
Express Middleware 7800 published
Read the important fields like this:
Field
Example
Plain English
views
15200
Sort key for stable pages
published
true
Filter before skipping
title
"REST API Design"
What we show in results
How It Works
Reset once.drop() keeps demos repeatable.
Mixed publish state. One draft is excluded by later $match.
find() shows the same shape. Ready for $skip next.
Example 2 — Skip After Sort
Goal: published articles, highest views first, drop the top 3 and show what remains (no page size yet).
Large skips. Very high offsets get expensive—use cursors for deep pages.
Edge Cases & Pitfalls
Check these before shipping paginated APIs with $skip.
order
$skip does not sort
Without $sort, the offset is in pipeline order—pages can reshuffle between requests.
cost
Large skips are expensive
Skipping 100,000 rows still walks past them. Prefer cursor/range pagination for deep pages.
ties
Add a tie-breaker for stable pages
Equal views can shuffle. Sort by views then _id (or createdAt) for consistency.
pair
Skip alone is rarely a page
Without $limit, everything after the offset is returned. Pair them for real pages.
Analysis
Performance Notes
Approach
Cost
Best for
Small $skip + $limit
Fine for early pages
Typical UI pagination
$match then skip
Fewer docs to walk
Filtered lists
Huge $skip
Reads discarded rows
Avoid—use cursors
Put selective $match first, keep $sort before $skip, and avoid growing offsets for deep navigation.
Remember
Key Takeaways
Role: discard the first N documents.
Pages:$sort → $skip → $limit.
Formula:skip = (page - 1) × pageSize.
Caution: large skips get expensive.
One line:$skip answers “start after the first N documents.”
Frequently Asked Questions
$skip discards the first N documents from the pipeline 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 } 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.
Yes. MongoDB must walk past every skipped document. For deep pages, prefer cursor-based pagination (filter by last _id or sort key) instead of ever-growing $skip values.
$skip removes documents from the start (offset). $limit caps how many continue (page size). Together they implement offset pagination; neither sorts—you need $sort for that.