Episode 11 of 17

Limit, Skip and Sort

Master cursor methods in MongoDB — limit(), skip(), and sort(). Learn how to paginate results, order data by fields, combine methods for efficient pagination, and understand cursor-based pagination for production apps.

When dealing with large collections, returning all documents at once is impractical. MongoDB provides cursor methods — limit(), skip(), and sort() — to control how many documents are returned, in what order, and from which offset. These are essential for building pagination in any application.

limit() — Restrict Number of Results

// Return only the first 5 users
db.users.find().limit(5)

// Combine with a filter
db.posts.find({ published: true }).limit(10)

// limit(0) means no limit (returns all documents)
db.users.find().limit(0)

skip() — Skip Documents

// Skip the first 10 documents
db.users.find().skip(10)

// Skip 5 and return the next 5
db.users.find().skip(5).limit(5)

sort() — Order Results

// Sort ascending (1 = A-Z, oldest first, lowest first)
db.users.find().sort({ name: 1 })

// Sort descending (-1 = Z-A, newest first, highest first)
db.users.find().sort({ createdAt: -1 })

// Sort by multiple fields
db.users.find().sort({ role: 1, name: 1 })
// First sort by role (A-Z), then by name within each role

// Sort by nested field
db.orders.find().sort({ "customer.name": 1 })

Combining Methods — Pagination

The most common use case is building pagination:

// Page 1 — first 10 results, newest first
db.posts.find({ published: true })
    .sort({ createdAt: -1 })
    .skip(0)
    .limit(10)

// Page 2 — results 11-20
db.posts.find({ published: true })
    .sort({ createdAt: -1 })
    .skip(10)
    .limit(10)

// Page 3 — results 21-30
db.posts.find({ published: true })
    .sort({ createdAt: -1 })
    .skip(20)
    .limit(10)

// Generic pagination formula:
// skip = (pageNumber - 1) * pageSize
// limit = pageSize

Pagination Helper Function

// In your application code:
function getPage(collection, filter, page, pageSize, sortField) {
    const skip = (page - 1) * pageSize;
    return db[collection]
        .find(filter)
        .sort({ [sortField]: -1 })
        .skip(skip)
        .limit(pageSize);
}

// Usage:
getPage("posts", { published: true }, 3, 10, "createdAt")
// Gets page 3 (posts 21-30), sorted by newest

Counting Total Pages

// Get total count for pagination metadata
const totalDocs = db.posts.countDocuments({ published: true });
const pageSize = 10;
const totalPages = Math.ceil(totalDocs / pageSize);

// Return to frontend:
// {
//     data: [...results],
//     pagination: {
//         currentPage: 3,
//         pageSize: 10,
//         totalDocs: 150,
//         totalPages: 15
//     }
// }

Method Execution Order

Regardless of the order you chain them, MongoDB always executes in this order:

  1. sort() — Orders the results first
  2. skip() — Then skips documents
  3. limit() — Then limits the count
// These produce the SAME result:
db.users.find().sort({ name: 1 }).skip(5).limit(10)
db.users.find().limit(10).sort({ name: 1 }).skip(5)
db.users.find().skip(5).limit(10).sort({ name: 1 })

Performance: The skip() Problem

Warning: skip() gets slower as the offset increases. For page 1000 with 10 items per page, MongoDB scans and discards 9,990 documents before returning 10. For large datasets, use cursor-based pagination instead:

Cursor-Based Pagination (Better for Large Datasets)

// Instead of skip(), use the last document's _id or timestamp

// First page
db.posts.find({ published: true })
    .sort({ createdAt: -1, _id: -1 })
    .limit(10)

// Next page — use the last document's createdAt and _id
db.posts.find({
    published: true,
    $or: [
        { createdAt: { $lt: lastCreatedAt } },
        {
            createdAt: lastCreatedAt,
            _id: { $lt: lastId }
        }
    ]
})
    .sort({ createdAt: -1, _id: -1 })
    .limit(10)

This is how production apps like Twitter, Facebook, and Instagram implement "infinite scroll" — it's consistently fast regardless of how deep you paginate.

Natural Sort Order

// $natural sort — returns documents in insertion order
db.logs.find().sort({ $natural: 1 })   // Oldest first (insertion order)
db.logs.find().sort({ $natural: -1 })  // Newest first (reverse)

Practical Examples

// Top 5 most viewed posts
db.posts.find({ published: true })
    .sort({ views: -1 })
    .limit(5)
    .project({ title: 1, views: 1, _id: 0 })

// Latest 3 orders for a customer
db.orders.find({ "customer.email": "john@example.com" })
    .sort({ createdAt: -1 })
    .limit(3)

// Products sorted by price (cheapest first), page 2
db.products.find({ inStock: true })
    .sort({ price: 1 })
    .skip(20)
    .limit(20)

What's Next

You can now paginate, sort, and efficiently retrieve subsets of data. In the next episode, we'll dive into indexing — the most important performance optimization in MongoDB, which makes sort() and find() operations dramatically faster.

TutorialMongoDBDatabaseNoSQL