MongoDB $first Accumulator

Beginner
⏱️ 9 min read
📚 Updated: Jul 2026
🎯 5 Examples
Aggregation

What You’ll Learn

The $first accumulator picks the value from the first document in each $group bucket—ideal for earliest sale dates, first order IDs, or opening prices when you control order with $sort.

01

First per group

Value from the leading doc.

02

Simple syntax

{ $first: "$field" }

03

Needs $sort

Order before $group matters.

04

vs $last

First vs last in order.

05

vs $bottom

Stream order vs in-group rank.

06

Null handling

Missing fields on first doc.

Definition and Usage

$first is a MongoDB accumulator used in $group (and related stages). It evaluates an expression on the first document that enters each group and returns that result.

💡
Beginner tip

$first does not sort documents itself. Without a preceding $sort, “first” means whatever order MongoDB happens to process—often unpredictable. Always sort first when you care about earliest, cheapest, or top-ranked records.

Common pattern: $sort by date ascending, then $group with { firstSale: { $first: "$date" } } to get the earliest sale per product. For ranked picks inside the group without a global sort, consider $bottom or $topN instead.

📝 Syntax

$first takes a single expression—the field or computed value to capture from the first document:

mongosh
{
  $group: {
    _id: <expression>,
    <field>: { $first: <expression> }
  }
}

Syntax Rules

  • One argument — pass any expression: field path, literal, or computed value.
  • Order matters — add $sort before $group to define what “first” means.
  • Scalar result — returns a single value per group (not an array), unless the expression itself is an array field.
  • Array fields — in $group, if the expression resolves to an array on the first doc, the entire array is returned (not the first element).
  • Missing values — if the first document lacks the field, $first returns null.
  • Stages — also works in $bucket, $bucketAuto, and as a window operator in $setWindowFields.
mongosh
db.sales.aggregate([
  { $sort: { item: 1, date: 1 } },
  {
    $group: {
      _id: "$item",
      firstSale: { $first: "$date" }
    }
  }
]);

⚡ Quick Reference

QuestionAnswer
Stage$group accumulator (also $bucket, windows)
Syntax{ $first: "$field" }
ReturnsOne value from the first doc per group
Earliest date pattern$sort: { date: 1 } then $first: "$date"
Full first document{ $first: "$$ROOT" }
Requires $sort?Strongly recommended whenever order matters
Earliest date
$sort: { date: 1 }
$first: "$date"

Sort asc → first = earliest

Latest date
$sort: { date: -1 }
$first: "$date"

Sort desc → first = latest

Full document
$first: "$$ROOT"

Entire first doc in group

Pair with $last
firstDate: { $first: "$date" }
lastDate:  { $last: "$date" }

Range endpoints per group

🧰 Parameters

$first accepts one expression argument. These related concepts control behavior:

expression Required

Field path or expression evaluated on the first document in each group. Common: "$date", "$price", or "$$ROOT".

{ $first: "$date" }
{ $first: { $add: ["$price", "$tax"] } }
$sort Prerequisite

Preceding stage that defines document order before $group. Without it, “first” is not reliably earliest or cheapest.

{ $sort: { item: 1, date: 1 } }
_id Group key

Standard $group bucket key—e.g. "$item" for per-product first sale dates.

_id: "$category"
missing / null Edge case

If the first document in a group lacks the target field, $first returns null. Empty strings and null values are returned as-is.

$sort before $group to control which doc is first

Examples Gallery

Sales records grouped by item—find the first sale date per product and explore ordering, comparisons, and edge cases.

📚 Getting Started

Sample sales data and the core $sort + $first pattern.

Example 1 — Sample sales collection

mongosh
db.sales.insertMany([
  { _id: 1, item: "abc", price: 10, quantity: 2,
    date: ISODate("2014-01-01T08:00:00Z") },
  { _id: 2, item: "jkl", price: 20, quantity: 1,
    date: ISODate("2014-02-03T09:00:00Z") },
  { _id: 3, item: "xyz", price: 5, quantity: 5,
    date: ISODate("2014-02-03T09:05:00Z") },
  { _id: 4, item: "abc", price: 10, quantity: 10,
    date: ISODate("2014-02-15T08:00:00Z") },
  { _id: 5, item: "xyz", price: 5, quantity: 10,
    date: ISODate("2014-02-15T09:05:00Z") },
  { _id: 6, item: "xyz", price: 5, quantity: 5,
    date: ISODate("2014-02-15T12:05:10Z") },
  { _id: 7, item: "xyz", price: 5, quantity: 10,
    date: ISODate("2014-02-15T14:12:12Z") }
]);

How It Works

Three products (abc, jkl, xyz) with multiple sale dates—perfect for verifying earliest-date results.

Example 2 — First sale date per item

mongosh
db.sales.aggregate([
  { $sort: { item: 1, date: 1 } },
  {
    $group: {
      _id: "$item",
      firstSale: { $first: "$date" }
    }
  },
  { $sort: { _id: 1 } }
]);

/* Result:
[
  { _id: "abc", firstSale: ISODate("2014-01-01T08:00:00Z") },
  { _id: "jkl", firstSale: ISODate("2014-02-03T09:00:00Z") },
  { _id: "xyz", firstSale: ISODate("2014-02-03T09:05:00Z") }
]
*/

How It Works

$sort orders by item then ascending date. Within each item group, the earliest sale reaches $group first—so $first captures that date.

📈 Practical Patterns

Compare with $last, return full documents, and handle missing fields.

Example 3 — $first vs $last (date range per item)

mongosh
db.sales.aggregate([
  { $sort: { item: 1, date: 1 } },
  {
    $group: {
      _id: "$item",
      firstSale: { $first: "$date" },
      lastSale:  { $last: "$date" }
    }
  }
]);

/* xyz example:
   firstSale: 2014-02-03T09:05:00Z
   lastSale:  2014-02-15T14:12:12Z */
*/

How It Works

With the same ascending sort, $first and $last bookend each group—handy for reporting active date ranges per product.

Example 4 — Return the full first document with $$ROOT

mongosh
db.sales.aggregate([
  { $sort: { item: 1, date: 1 } },
  {
    $group: {
      _id: "$item",
      firstOrder: { $first: "$$ROOT" }
    }
  }
]);

// firstOrder for abc includes _id: 1, price: 10,
// quantity: 2, and the earliest date fields

How It Works

$$ROOT preserves the entire first document—useful when downstream stages need price, quantity, and date together.

Example 5 — Missing and null values

When the first document in a group lacks a field, $first returns null:

mongosh
db.badData.insertMany([
  { _id: 1, price: 6, quantity: 6 },
  { _id: 2, item: "album", price: 5, quantity: 5 },
  { _id: 7, item: "tape", price: 6, quantity: 6 },
  { _id: 8, price: 5, quantity: 5 },
  { _id: 9, item: "album", price: 3, quantity: "" },
  { _id: 10, item: "tape", price: 3, quantity: 4 },
  { _id: 12, item: "cd", price: 7 }
]);

db.badData.aggregate([
  { $sort: { item: 1, price: 1 } },
  {
    $group: {
      _id: "$item",
      inStock: { $first: "$quantity" }
    }
  }
]);

/* cd group → inStock: null (no quantity on first cd doc)
   album → inStock: "" (empty string preserved) */
*/

How It Works

Sort order decides which document is “first.” If that document is missing the field, you get null—not the next document in the group.

🧠 How $first Works

1

Documents sorted

$sort (recommended) defines the order documents flow into $group.

Sort
2

Documents bucketed

$group assigns each document to a bucket via _id.

Group
3

First doc captured

For each bucket, $first evaluates its expression on the leading document only.

Capture
=

Value returned

One result per group—the expression value from that first document.

📝 Notes

  • Always $sort first when “first” must mean earliest, cheapest, or highest-ranked by your rules.
  • $first is order-dependent; it does not scan for min/max across the whole group.
  • For minimum numeric values regardless of order, use $min instead.
  • Array fields return the whole array from the first document in $group, not the first array element.
  • Documents missing the grouped field still form a group with _id: null.
  • Previous topic: $bottomN. Next: $firstN.

Conclusion

$first is the straightforward way to grab a value from the leading document in each $group bucket. Pair it with $sort so “first” matches your business meaning—earliest sale, first login, opening price.

Combine with $last for date ranges, or move to $firstN when you need several leading documents per group.

💡 Best Practices

✅ Do

  • Add $sort immediately before $group when order matters
  • Include tie-break sort keys (_id, name) for stable ordering
  • Use $first + $last together for per-group time ranges
  • Use $$ROOT when you need the full first record, not one field
  • Put $match before $sort to reduce documents sorted

❌ Don’t

  • Assume $first returns the minimum value—it follows document order
  • Skip $sort and expect consistent “earliest” results
  • Confuse $first with $firstN (one value vs array of N)
  • Expect $first to skip null on later documents—it only reads the first doc
  • Use $first for ranked picks when $bottom/$top fit better

Key Takeaways

Knowledge Unlocked

Five things to remember about $first

Use these points when capturing leading values inside $group.

5
Core concepts
📅 02

$sort first

Define what first means.

Pattern
🔄 03

vs $last

Range endpoints.

Compare
📦 04

$$ROOT

Full first document.

Output
⚠️ 05

Null on miss

First doc missing field.

Edge case

❓ Frequently Asked Questions

$first returns the value of an expression from the first document in each $group bucket—based on the order documents arrive at $group. Use it for earliest dates, first order IDs, or opening prices per category.
Yes, in almost every real pipeline. $first does not sort for you—it takes whatever document is first in the current order. Add a $sort stage before $group so first means what you expect (e.g. earliest date).
$first reads the first document in each group; $last reads the last. With $sort: { date: 1 }, $first gives the earliest date and $last gives the latest.
$first follows pipeline document order (usually set by $sort). $bottom ranks inside $group with its own sortBy and picks one bottom-ranked document. Use $bottom for explicit ranking; use $first when you already sorted the stream.
$first returns null when the expression resolves to a missing field on that first document. Missing values are not skipped—the first doc is still used.
As an accumulator in $group, $bucket, and $bucketAuto. It is also a window operator in $setWindowFields (MongoDB 5.0+) with sortBy on the window.
Did you know?

Before MongoDB 5.2’s $top/$bottom family, the classic recipe for “best record per group” was $sort + $group + $first. That pattern still works—and $first remains essential for time-series open/close values. See the official $first docs.

Continue the Accumulators Series

Capture single leading values with $first, then learn $firstN for the first N documents per group.

Next: $firstN →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful