Projection, Sort, Limit & Skip

MongoDB · Lesson 9 of 28 7 min read Updated

A query decides which documents you get. Three more tools decide how you get them: projection chooses the fields, sort chooses the order, and limit and skip choose how many and from where. Together they turn a raw query into something a page can display, such as "the three most expensive products, showing only name and price".

In this lesson you will use all three on the shop data and build a simple pagination system, then a faster one. Start with freshly loaded data and use shop.

Projection: choosing fields

The second argument of find() is the projection. Set a field to 1 to include it or 0 to exclude it. In an inclusion projection you list the fields you want; _id comes along automatically unless you switch it off:

MongoDB Shell
db.customers.find({ city: 'Kochi' }, { name: 1, email: 1, _id: 0 })
Output
[
  { name: 'Priya Nair', email: 'priya@example.com' },
  { name: 'Isha Verma', email: 'isha@example.com' }
]

In an exclusion projection you list the fields you do not want and get everything else:

MongoDB Shell
db.products.find({ category: 'Storage' }, { tags: 0, specs: 0, rating: 0 })
Output
[
  {
    _id: 107,
    name: 'Portable SSD 1TB',
    category: 'Storage',
    price: 6999,
    stock: 25
  }
]

You cannot mix the two styles: { name: 1, stock: 0 } makes the server reject the query with an error about an exclusion inside an inclusion projection. The only exception is _id, which you may exclude from an inclusion projection, as in the first example.

Projection saves network traffic and memory, and it keeps private fields out of places they do not belong. A product list page never needs a customer's email, so do not fetch it. Note that the returned fields keep the order they have in the stored document, not the order you wrote in the projection.

Projecting embedded fields and arrays

Dot notation works in projections too. Aarav's orders, showing only the date and the item names:

MongoDB Shell
db.orders.find({ customerId: 1 }, { date: 1, 'items.name': 1 })
Output
[
  {
    _id: 1001,
    date: ISODate('2026-08-03T10:15:00.000Z'),
    items: [ { name: 'Wireless Mouse' }, { name: 'USB-C Cable' } ]
  },
  {
    _id: 1003,
    date: ISODate('2026-08-15T09:05:00.000Z'),
    items: [ { name: 'Portable SSD 1TB' } ]
  },
  {
    _id: 1011,
    date: ISODate('2026-10-19T20:25:00.000Z'),
    items: [ { name: 'Mechanical Keyboard' } ]
  }
]

For arrays there are also projection operators. $slice returns part of an array, such as { name: 1, tags: { $slice: 1 } } for only the first tag, and $elemMatch returns only the first array element that matches a condition.

A projection can also calculate new fields with aggregation expressions. A price list including 18% GST, rounded to whole rupees:

MongoDB Shell
db.products.find(
  { category: 'Accessories', price: { $lt: 1500 } },
  { name: 1, price: 1, priceWithGst: { $round: [{ $multiply: ['$price', 1.18] }, 0] } }
)
Output
[
  { _id: 101, name: 'Wireless Mouse', price: 799, priceWithGst: 943 },
  { _id: 104, name: 'USB-C Cable', price: 299, priceWithGst: 353 },
  { _id: 106, name: 'Laptop Stand', price: 1299, priceWithGst: 1533 }
]

The calculated field exists only in the result; nothing is stored. Calculated fields are added after the fields that come from the document.

sort(): ordering results

Chain sort() onto find() and give it fields with 1 for ascending (A to Z, small to large, old to new) or -1 for descending. With limit() you get "top N" lists. The three most expensive products:

MongoDB Shell
db.products.find({}, { name: 1, price: 1 }).sort({ price: -1 }).limit(3)
Output
[
  { _id: 103, name: '27-inch Monitor', price: 15999 },
  { _id: 107, name: 'Portable SSD 1TB', price: 6999 },
  { _id: 102, name: 'Mechanical Keyboard', price: 3499 }
]

Sort by several fields by listing them in order of importance. The first field sorts the documents; the second sorts only within groups that tie on the first:

MongoDB Shell
db.products.find({}, { _id: 0, category: 1, price: 1 }).sort({ category: 1, price: -1 })
Output
[
  { category: 'Accessories', price: 3499 },
  { category: 'Accessories', price: 1899 },
  { category: 'Accessories', price: 1299 },
  { category: 'Accessories', price: 799 },
  { category: 'Accessories', price: 299 },
  { category: 'Audio', price: 2499 },
  { category: 'Books', price: 649 },
  { category: 'Displays', price: 15999 },
  { category: 'Mobile', price: 1499 },
  { category: 'Storage', price: 6999 }
]

Categories run A to Z, and inside Accessories the prices run high to low. Sorting is not stable: if two documents tie on every sort field, their order can change between runs. Add a unique field such as _id as the last sort key whenever you need a fixed order.

Strings sort by their character codes, so 'Zebra' comes before 'apple'. For language-aware, case-insensitive sorting, add a collation: .collation({ locale: 'en', strength: 2 }).

limit() and skip(): pagination

limit(n) returns at most n documents. skip(n) jumps over the first n. Together they make pages. With three products per page, sorted by price, page 2 skips the first three:

MongoDB Shell
db.products.find({}, { name: 1, price: 1 }).sort({ price: 1, _id: 1 }).skip(3).limit(3)
Output
[
  { _id: 106, name: 'Laptop Stand', price: 1299 },
  { _id: 110, name: 'Power Bank 20000mAh', price: 1499 },
  { _id: 109, name: 'HD Webcam', price: 1899 }
]

For page number p (starting at 1) and page size s, use skip((p - 1) * s).limit(s). MongoDB always applies the steps in the same logical order, sort, then skip, then limit, no matter which order you chain the methods in.

Advanced: range-based pagination

skip() is simple but gets slower as the page number grows, because the server still has to walk past every skipped document. Page 5,000 of a big catalogue means stepping over 1,00,000 documents first.

The faster method remembers where the last page ended and asks for documents after it. Page 1 ended with the Wireless Mouse at ₹799, so page 2 is:

MongoDB Shell
db.products.find({ price: { $gt: 799 } }, { name: 1, price: 1 }).sort({ price: 1, _id: 1 }).limit(3)
Output
[
  { _id: 106, name: 'Laptop Stand', price: 1299 },
  { _id: 110, name: 'Power Bank 20000mAh', price: 1499 },
  { _id: 109, name: 'HD Webcam', price: 1899 }
]

Same result, but with an index on price the server jumps straight to the right place, so page 5,000 is as fast as page 2. If prices can repeat, include the _id in the condition so no product is skipped: { $or: [{ price: { $gt: 799 } }, { price: 799, _id: { $gt: 101 } }] }. This style, also called keyset or cursor pagination, is what infinite-scroll feeds use. Its limitation is that you can only move to the next or previous page, not jump to page 37.

Common mistakes

  • Mixing inclusion and exclusion. { name: 1, stock: 0 } is an error. Choose one style; only _id: 0 may be mixed in.
  • Using limit() without sort(). "The first three" means nothing without an order. Always sort before limiting.
  • Sorting on a non-unique field for pagination. Ties can move between pages. Add _id as a final sort key.
  • Large skip() values. They get slower page by page. Use range-based pagination for long lists.

Key points

  • The projection (second argument of find()) includes fields with 1 or excludes them with 0; _id is included unless you set _id: 0.
  • Inclusion and exclusion cannot be mixed, except for _id; projections can also use $slice and calculated expressions.
  • sort({ field: 1 }) sorts ascending and -1 descending; later fields only break ties.
  • limit() caps the number of results and skip() skips some; the order is always sort, skip, limit.
  • Add _id as a final sort key for stable pages, and prefer range-based pagination for deep pages.

Exercise

Show the three cheapest products that are in stock (stock greater than 0), with only their names and prices and no _id. Then show the four most recent orders, newest first, with only their date, status and total.

Show solution
MongoDB Shell
db.products.find({ stock: { $gt: 0 } }, { _id: 0, name: 1, price: 1 }).sort({ price: 1 }).limit(3)
db.orders.find({}, { date: 1, status: 1, total: 1 }).sort({ date: -1 }).limit(4)

The cheapest in-stock products are the USB-C Cable (₹299), the Learn Python Book (₹649) and the Wireless Mouse (₹799). The newest orders are 1012, 1011, 1010 and 1009. Sorting by date with -1 puts the latest date first.

Quick Quiz

1. Which projection returns only name and price, without _id?

2. In sort({ category: 1, price: -1 }), what does price: -1 do?

3. Which call returns page 3 when each page has 10 documents?

4. Why is range-based pagination faster than skip() for deep pages?

Learn MongoDB with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call