MongoDB $first Accumulator

Beginner
5 min read
Updated: Oct 2026
4 examples
Live preview

What Is $first?

$first is a MongoDB accumulator for the $group stage. It evaluates an expression on the first document that enters each group and returns that value—ideal for earliest sale dates or opening prices when you control order with $sort.

Remember
$first = value from the first document in each group

order → set with $sort before $group (required in practice)
expression → usually "$field" or "$$ROOT"

vs $last → first vs last document in that same order
vs $bottom → stream order vs in-group sortBy ranking
UsePrefer
Earliest / first value after a sort$first
Latest / last value after a sort$last
First N values as an array$firstN
Rank inside the group (no prior sort)$bottom / $top

How to Use $first in MongoDB

Sort first, then capture one field (or the whole document) per group.

  1. Add a $sort stage so “first” means what you expect (e.g. earliest date).
  2. Put $first inside $group on an output field.
  3. Pass a field path (e.g. "$date") or "$$ROOT" for the full document.
  4. Choose _id for the group key (item, category, or null for one group).
PieceRequired?Role
$sort (before)In practice yesDefines document order for “first”
$group._idYesGroup key ("$item", null, …)
$firstYesExpression evaluated on the first doc

Pseudocode

Pseudocode
documents = sort(documents, sortSpec)   // e.g. date ascending

for each group:
    firstDoc = first document that enters the group
    value = evaluate(expression, firstDoc)
    emit { _id: groupKey, field: value }

Cheat Sheet

Keep these facts handy while writing a first-value report.

TopicDetail
Stage$group (also $bucket / $bucketAuto)
Syntax{ $first: "$field" }
OrderSet with $sort before $group
vs $lastFirst vs last document in that order
Full document{ $first: "$$ROOT" }
Missing fieldReturns null (does not skip to next doc)
Sample checkitem abc dates → firstSale = 2014-01-01

Live Preview

Watch $sort by date, then $first pick the earliest sale. Sample items: abc, jkl, xyz.

Simulates $sort: { date: 1 } then { $first: "$date" } per item (same idea as the mongosh examples below).

Result
abc dates asc → 2014-01-01, 2014-02-15
$first → 2014-01-01

jkl dates asc → 2014-02-03
$first → 2014-02-03

xyz dates asc → 2014-02-03, 2014-02-15, 2014-02-15, 2014-02-15
$first → 2014-02-03

[
  { _id: "abc", firstSale: "2014-01-01" },
  { _id: "jkl", firstSale: "2014-02-03" },
  { _id: "xyz", firstSale: "2014-02-03" }
]

Worked Walkthrough — Earliest Sale for abc

Two abc sales. After sorting by date ascending, the January document is first—so $first keeps that date.

Calculation
Documents (item "abc"):
  { date: 2014-01-01, price: 10, quantity: 2 }
  { date: 2014-02-15, price: 10, quantity: 10 }

$sort: { item: 1, date: 1 }
  → January sale arrives first

$group _id: "$item"
  firstSale: { $first: "$date" }
  → 2014-01-01

Output:
  { _id: "abc", firstSale: ISODate("2014-01-01T08:00:00Z") }

Without $sort, "first" is undefined —
whatever order MongoDB happens to process.

MongoDB Examples

We use a tiny sales collection. First we get the earliest sale date per item, then compare with $last, then keep the full first document with $$ROOT. Run these in mongosh after inserting the sample data once.

Example 1 — Sample Data

Three products with multiple sale dates. Item abc has a clear earliest sale in January—perfect for checking $first.

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 the Data Is Stored in MongoDB

After the insert, MongoDB keeps 7 documents in sales. Run find() to see them:

mongosh
db.sales.find().sort({ _id: 1 })

Read each document like this:

Field in MongoDBMeaningExample
_idSale id1
itemProduct code"abc"
priceUnit price10
quantityHow many sold2
dateWhen the sale happened2014-01-01
Picture
Sales by item (earliest first after $sort):

  abc: Jan 1, then Feb 15
  jkl: Feb 3
  xyz: Feb 3, then three on Feb 15

Next: $first keeps the earliest sale date per item.

How It Works

  1. Insert once. insertMany adds seven sale documents to sales.
  2. MongoDB stores fields as written. Each sale has item, price, quantity, and date.
  3. find() shows the same shape. What you inserted is what you get back—ready for $first after a $sort.

Example 2 — First Sale Date per Item

Goal: for each item, return the earliest sale date. $sort makes “first” mean earliest.

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

How It Works

  1. Sort the stream. { item: 1, date: 1 } groups items together and puts earlier dates first within each item.
  2. Bucket by item. _id: "$item" creates groups for abc, jkl, and xyz.
  3. Take the leader. The first document into each group is the earliest sale; $first: "$date" keeps that date.
  4. Stable report order. Final $sort on _id lists items alphabetically (group order alone is not guaranteed).

Example 3 — $first vs $last

Same ascending date sort, two accumulators: earliest and latest sale per item.

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

How It Works

  1. One sort, two ends. Ascending dates make the earliest document first and the latest document last in each group.
  2. $first bookends the start. For xyz, that is Feb 3 at 09:05.
  3. $last bookends the end. For xyz, that is Feb 15 at 14:12—handy for active date ranges.
  4. Same idea, opposite side. Prefer $last when you only need the closing value.

Example 4 — Full First Document with $$ROOT

Goal: keep the entire earliest sale document (price, quantity, and date together), not just one field.

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

How It Works

  1. Same sort + group. Earliest sale still arrives first in each item bucket.
  2. $$ROOT is the whole doc. Instead of "$date", $first stores the complete first document under firstOrder.
  3. Useful for multi-field reports. Downstream stages can read price, quantity, and date without extra accumulators.
  4. Heavier than a field. Prefer a single field when you only need one value—full documents use more memory.

Edge Cases & Pitfalls

Check these before shipping a first-value report.

order

Always sort before $group

Without $sort, “first” is whatever order MongoDB processes—often unpredictable across runs.

null

Missing fields become null

If the first document lacks the field, $first returns null. It does not skip to the next document in the group.

arrays

Array fields stay whole

In $group, if the expression is an array on the first doc, the entire array is returned—not the first element.

rank

Need in-group ranking?

Use $bottom / $top with sortBy when ranking should happen inside the group, not via a prior stream sort.

Performance Notes

ApproachCostBest for
$sort + $firstSort cost + one value per groupEarliest / first field after a defined order
$first: "$$ROOT"Stores a full document per groupMulti-field first records
$top / $bottomIn-group ranking (no prior sort needed)Ranked pick when stream order is unset

Prefer a single field over $$ROOT when you only need one value. Filter early with $match so the sort scans fewer documents.

Key Takeaways

  • Role: value from the first document in each $group bucket.
  • Order: set with $sort before $group—$first does not sort.
  • vs $last: first vs last document in that same order.
  • Full doc: use { $first: "$$ROOT" } when you need every field.

One line: $first keeps one value from the leading document in each group—sort first so “first” means earliest, cheapest, or top of your list.

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 (for example, 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. Use $bottom for explicit in-group 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.

Next: $firstN

Return the first N values from each group as an array.

$firstN tutorial →

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.

9 people found this page helpful