mongodb
pymongo
sorting
python
database

How to sort mongodb with pymongo

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Sorting in MongoDB with PyMongo is easy to write and easy to get subtly wrong. The API call itself is small, but correct sorting also depends on tie-breakers, index support, and whether the query is part of pagination, aggregation, or user-facing text ordering.

Basic sort() Syntax

PyMongo uses ASCENDING and DESCENDING constants for readability:

python
1from pymongo import MongoClient, ASCENDING, DESCENDING
2
3client = MongoClient("mongodb://localhost:27017")
4db = client["demo_db"]
5col = db["orders"]
6
7for doc in col.find({}).sort("total", ASCENDING):
8    print(doc["order_id"], doc["total"])

ASCENDING is equivalent to 1, and DESCENDING is equivalent to -1, so these are equivalent:

python
col.find({}).sort("total", 1)
col.find({}).sort("total", ASCENDING)

The constant form is usually easier to read.

Prepare Sample Data

python
1col.delete_many({})
2col.insert_many([
3    {"order_id": 1, "customer": "A", "total": 50, "created_at": 10},
4    {"order_id": 2, "customer": "C", "total": 20, "created_at": 9},
5    {"order_id": 3, "customer": "B", "total": 50, "created_at": 11},
6])

Now you can sort by one field:

python
for doc in col.find({}).sort("total", DESCENDING):
    print(doc["order_id"], doc["total"])

Multi-Field Sorting

If multiple documents share the same primary sort value, add more fields so the order is deterministic:

python
1cursor = col.find({}).sort([
2    ("total", DESCENDING),
3    ("created_at", ASCENDING),
4    ("order_id", ASCENDING),
5])
6
7for doc in cursor:
8    print(doc["order_id"], doc["total"], doc["created_at"])

This says:

  • sort highest total first
  • for equal totals, sort earliest created_at first
  • for remaining ties, sort by order_id

That last tie-breaker is often important for stable pagination.

Sorting with Filters

Sorting is commonly combined with a query filter:

python
1cursor = col.find({"customer": "A"}).sort("created_at", DESCENDING)
2
3for doc in cursor:
4    print(doc["order_id"], doc["created_at"])

This pattern is typical for timelines, order history, audit logs, or activity feeds.

Sorting in Aggregation Pipelines

You can also sort inside an aggregation pipeline:

python
1pipeline = [
2    {"$match": {"total": {"$gte": 20}}},
3    {"$sort": {"total": -1, "order_id": 1}},
4    {"$project": {"_id": 0, "order_id": 1, "total": 1}},
5]
6
7for doc in col.aggregate(pipeline):
8    print(doc)

Aggregation sorting is useful when the sort happens after filtering, grouping, projection, or computed fields.

Sorting a Single Best Match

When you only need one top-ranked document, find_one also accepts sort information:

python
1latest_order = col.find_one(
2    {"customer": "A"},
3    sort=[("created_at", DESCENDING), ("order_id", DESCENDING)],
4)
5
6print(latest_order)

This can be cleaner than calling find().sort(...).limit(1) when the intent is specifically "give me the best match."

Sorting Strings with Collation

If user-facing string order matters, collation can change sort behavior for case and locale rules:

python
1from pymongo.collation import Collation
2
3case_insensitive = Collation(locale="en", strength=2)
4
5for doc in col.find({}).sort("customer", ASCENDING).collation(case_insensitive):
6    print(doc["customer"])

Without collation, string ordering may not match user expectations, especially with mixed case or non-English alphabets.

Indexes Matter

Sorting large collections without a supporting index can be expensive. If a query pattern is common, build an index that matches the filter and sort order.

python
1col.create_index([
2    ("customer", ASCENDING),
3    ("created_at", DESCENDING),
4])

Then a query like:

python
cursor = col.find({"customer": "A"}).sort("created_at", DESCENDING)

has a much better chance of using the index efficiently.

For diagnosis, inspect the query plan:

python
plan = col.find({"customer": "A"}).sort("created_at", DESCENDING).explain()
print(plan["queryPlanner"]["winningPlan"])

Sorting and Pagination

The naive pagination pattern is:

python
1page = 2
2size = 10
3
4cursor = (
5    col.find({})
6    .sort([("created_at", DESCENDING), ("order_id", ASCENDING)])
7    .skip((page - 1) * size)
8    .limit(size)
9)

This is fine for modest page counts, but large skip() values get slower because MongoDB still has to walk past the skipped documents. For deep pagination, range-based pagination using the last-seen sort key is usually better.

Common Pitfalls

The most common mistake is sorting without a tie-breaker. If many documents share the same sort value, the order can appear unstable across requests, which becomes especially painful in pagination.

Another issue is assuming that sort() alone guarantees good performance. On large collections, missing indexes can turn a simple-looking query into an expensive in-memory sort.

Finally, do not forget that collation affects string sorting semantics. If users expect case-insensitive or locale-aware ordering, plain binary sorting may look incorrect even though the query "worked."

Summary

  • Use sort(field, ASCENDING) or sort(field, DESCENDING) for basic ordering.
  • Add secondary sort keys for deterministic results.
  • Align indexes with common filter-plus-sort query patterns.
  • Use collation when text sorting must follow locale or case rules.
  • Be careful with deep pagination, because large skip() values do not scale well.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.