$first and $last accumulators in MongoDB - Time & Space Complexity
When using $first and $last accumulators in MongoDB aggregation, it's important to understand how the processing time changes as the data grows.
We want to know how the number of operations changes when the input size increases.
Analyze the time complexity of the following MongoDB aggregation snippet.
db.collection.aggregate([
{ $sort: { category: 1, date: 1 } },
{ $group: {
_id: "$category",
firstEntry: { $first: "$value" },
lastEntry: { $last: "$value" }
}
}
])
This code sorts documents by category and date, then groups by category to find the first and last values in each group.
Look for repeated actions that affect performance.
- Primary operation: Sorting all documents by category and date.
- How many times: The sort processes all n documents once.
- Grouping: Scans sorted documents once to find first and last per group.
As the number of documents grows, the sorting step takes more time, while grouping scans through the sorted list once.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 log 10 (sort) + 10 (group) |
| 100 | About 100 log 100 + 100 |
| 1000 | About 1000 log 1000 + 1000 |
Pattern observation: The sorting dominates and grows a bit faster than the number of documents, while grouping grows linearly.
Time Complexity: O(n log n)
This means the time to run this aggregation grows a bit faster than the number of documents because of sorting.
[X] Wrong: "$first and $last run in constant time regardless of input size."
[OK] Correct: While $first and $last themselves pick values quickly, the sorting step before grouping takes more time as data grows, affecting overall speed.
Understanding how sorting affects aggregation helps you explain performance in real projects and shows you can think about data size impact clearly.
What if we removed the $sort stage before grouping? How would the time complexity change?
Practice
$first accumulator do in a MongoDB aggregation pipeline?Solution
Step 1: Understand the role of $first
The$firstaccumulator returns the first value of a specified field from documents in a group.Step 2: Differentiate from other accumulators
Unlike$last, which returns the last value,$firstpicks the earliest value based on the document order.Final Answer:
It returns the first value of a specified field in each group. -> Option DQuick Check:
$first= first value [OK]
- Confusing $first with $last accumulator
- Thinking $first sorts documents automatically
- Assuming $first counts documents
$last in a MongoDB aggregation $group stage?Solution
Step 1: Identify correct accumulator usage
The$lastaccumulator must be used with a field path as a string, like"$item".Step 2: Check syntax correctness
{ $group: { _id: "$category", lastItem: { $last: "$item" } } } correctly uses$last: "$item"inside$group. Options A and C have syntax errors, and B uses$firstinstead of$last.Final Answer:
{ $group: { _id: "$category", lastItem: { $last: "$item" } } } -> Option CQuick Check:
Correct $last syntax = { $group: { _id: "$category", lastItem: { $last: "$item" } } } [OK]
- Omitting quotes around field name
- Using $first instead of $last by mistake
- Writing accumulator name incorrectly
sales with documents:{ "_id": 1, "product": "A", "date": "2023-01-01" },
{ "_id": 2, "product": "A", "date": "2023-01-05" },
{ "_id": 3, "product": "B", "date": "2023-01-02" }What will be the output of this aggregation?
[
{ $sort: { date: 1 } },
{ $group: { _id: "$product", firstSale: { $first: "$date" }, lastSale: { $last: "$date" } } }
]Solution
Step 1: Sort documents by date ascending
The $sort stage orders documents by date: A on 2023-01-01, B on 2023-01-02, A on 2023-01-05.Step 2: Group by product and apply $first and $last
For product A, firstSale is earliest date "2023-01-01", lastSale is latest date "2023-01-05". For B, both firstSale and lastSale are "2023-01-02".Final Answer:
[{ "_id": "A", "firstSale": "2023-01-01", "lastSale": "2023-01-05" }, { "_id": "B", "firstSale": "2023-01-02", "lastSale": "2023-01-02" }] -> Option BQuick Check:
Sorted + grouped $first/$last = [{ "_id": "A", "firstSale": "2023-01-01", "lastSale": "2023-01-05" }, { "_id": "B", "firstSale": "2023-01-02", "lastSale": "2023-01-02" }] [OK]
- Ignoring $sort stage before $group
- Mixing up $first and $last values
- Assuming $first/$last pick random values
$last values:db.orders.aggregate([
{ $group: { _id: "$customer", lastOrder: { $last: "$orderDate" } } }
])What is the most likely reason?
Solution
Step 1: Understand $last depends on document order
The$lastaccumulator returns the last value in the current document order within each group.Step 2: Identify missing $sort stage
Without sorting byorderDate, documents are in arbitrary order, so$lastmay not return the latest date.Final Answer:
You forgot to sort documents byorderDatebefore grouping. -> Option AQuick Check:
Missing $sort before $group causes wrong $last [OK]
- Assuming $last finds max value automatically
- Using $last without sorting documents
- Confusing $last with $max accumulator
events with documents:{ "user": "Alice", "timestamp": 100 },
{ "user": "Alice", "timestamp": 200 },
{ "user": "Bob", "timestamp": 150 },
{ "user": "Bob", "timestamp": 50 }You want to find each user's first and last event timestamps using the $first and $last accumulators. Which aggregation pipeline correctly achieves this?
Solution
Step 1: Sort events by timestamp ascending
Sorting bytimestamp: 1ensures earliest events come first for each user.Step 2: Group by user and use $first and $last
Using$firstand$lastafter sorting picks the earliest and latest timestamps correctly.Final Answer:
[ { $sort: { timestamp: 1 } }, { $group: { _id: "$user", firstEvent: { $first: "$timestamp" }, lastEvent: { $last: "$timestamp" } } } ] -> Option AQuick Check:
Sort ascending + $first/$last = [ { $sort: { timestamp: 1 } }, { $group: { _id: "$user", firstEvent: { $first: "$timestamp" }, lastEvent: { $last: "$timestamp" } } } ] [OK]
- Skipping $sort stage before $group
- Sorting descending when $first expects ascending
- Using $min/$max instead of $first/$last when order matters
Ready to prove it?
Pass the test and this pattern moves unknown
