Projection, Sort, Limit & Skip
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:
db.customers.find({ city: 'Kochi' }, { name: 1, email: 1, _id: 0 })[
{ 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:
db.products.find({ category: 'Storage' }, { tags: 0, specs: 0, rating: 0 })[
{
_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:
db.orders.find({ customerId: 1 }, { date: 1, 'items.name': 1 })[
{
_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:
db.products.find(
{ category: 'Accessories', price: { $lt: 1500 } },
{ name: 1, price: 1, priceWithGst: { $round: [{ $multiply: ['$price', 1.18] }, 0] } }
)[
{ _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:
db.products.find({}, { name: 1, price: 1 }).sort({ price: -1 }).limit(3)[
{ _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:
db.products.find({}, { _id: 0, category: 1, price: 1 }).sort({ category: 1, price: -1 })[
{ 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:
db.products.find({}, { name: 1, price: 1 }).sort({ price: 1, _id: 1 }).skip(3).limit(3)[
{ _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:
db.products.find({ price: { $gt: 799 } }, { name: 1, price: 1 }).sort({ price: 1, _id: 1 }).limit(3)[
{ _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: 0may be mixed in. - Using
limit()withoutsort(). "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
_idas 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 with1or excludes them with0;_idis included unless you set_id: 0. - Inclusion and exclusion cannot be mixed, except for
_id; projections can also use$sliceand calculated expressions. sort({ field: 1 })sorts ascending and-1descending; later fields only break ties.limit()caps the number of results andskip()skips some; the order is always sort, skip, limit.- Add
_idas 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
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?
_id is included by default and must be excluded explicitly; mixing stock: 0 with inclusions is an error.
2. In sort({ category: 1, price: -1 }), what does price: -1 do?
Later sort keys only order documents that tie on the earlier keys.
3. Which call returns page 3 when each page has 10 documents?
Pages 1 and 2 hold the first 20 documents, so page 3 skips 20 and takes 10.
4. Why is range-based pagination faster than skip() for deep pages?
With an index, a condition such as price > 799 jumps straight to the next page.
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
