MongoDB $bottomN Accumulator

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

What Is $bottomN?

$bottomN is a MongoDB accumulator for the $group stage. It ranks documents in each group with sortBy, then returns the last N documents as an array—ideal for bottom-3 scorers or cheapest products.

Remember
$bottomN = last N docs after sortBy (per group)

n       → how many bottom documents to keep
sortBy  → ranking (same style as $sort)
output  → shape of each array element

vs $bottom → one document; $bottomN → array of up to N
MongoDB 5.2+
UsePrefer
Bottom-k ranked list per group$bottomN
Single worst / lowest record$bottom
Top-k leaderboard$topN
N smallest numbers only (no full docs)$minN

How to Use $bottomN in MongoDB

Three keys drive every $bottomN call inside $group.

  1. Put $bottomN inside $group on an output field.
  2. Set n to how many bottom-ranked documents you want.
  3. Set sortBy so “bottom” matches your ranking (for lowest scores: { score: -1 }).
  4. Set output to the fields each array element should include.
KeyRequired?Role
nYesPositive integer (or expression from group _id)
sortByYesSort spec like $sort (1 / -1)
outputYesProjection, field path, or array of fields

Pseudocode

Pseudocode
for each group:
    ranked = sort(documents, sortBy)
    bottom = last n items of ranked   // or all if fewer than n
    result = [ output(doc) for doc in bottom ]
    emit { _id: groupKey, field: result }

Cheat Sheet

Keep these facts handy while writing a bottom-N report.

TopicDetail
Stage$group only (as an accumulator)
Minimal keysn, sortBy, output
Lowest scoressortBy: { score: -1 } (high first → lows at bottom)
vs $bottomOne document vs array of up to N
Short groupsReturns all docs if group size < n
VersionMongoDB 5.2+
Sample checkG1 scores 99, 33, 31, 1 → bottom 3: 33, 31, 1

Live Preview

Watch $bottomN pick the three lowest scores after a descending sortBy. Sample: G1 (99, 33, 31, 1) and G2 (80, 66, 14, 10).

Simulates sortBy: { score: -1 } then taking the last 3 (same idea as the mongosh examples below).

Result
G1 sort desc: 99 → 33 → 31 → 1
bottom 3 → [33, 31, 1]

G2 sort desc: 80 → 66 → 14 → 10
bottom 3 → [66, 14, 10]

[
  { _id: "G1", bottomThree: [["PlayerB", 33], ["PlayerA", 31], ["PlayerD", 1]] },
  { _id: "G2", bottomThree: [["PlayerC", 66], ["PlayerB", 14], ["PlayerA", 10]] }
]

Worked Walkthrough — Bottom 3 in Game G1

Four G1 scores. Descending sort puts 99 first; the bottom three slots hold the lowest scores.

Calculation
Documents (game G1):
  PlayerC 99, PlayerB 33, PlayerA 31, PlayerD 1

sortBy: { score: -1 }  (high → low)
  99 → 33 → 31 → 1

bottom n = 3  (last three in that order)
  33, 31, 1

output: ["$playerId", "$score"]
  [["PlayerB", 33], ["PlayerA", 31], ["PlayerD", 1]]

Note: "bottom" means end of the sorted list —
not "sort ascending and take the first N."

MongoDB Examples

We use a tiny gamescores collection. First we take the three lowest scorers in one game, then per game, then compare with $bottom. Run these in mongosh after inserting the sample data once.

Example 1 — Sample Data

Two games, four players each. Notice G1 has a clear high score (99) and a clear low score (1)—perfect for checking bottom-3 results.

mongosh
db.gamescores.insertMany([
  { playerId: "PlayerA", gameId: "G1", score: 31 },
  { playerId: "PlayerB", gameId: "G1", score: 33 },
  { playerId: "PlayerC", gameId: "G1", score: 99 },
  { playerId: "PlayerD", gameId: "G1", score: 1 },
  { playerId: "PlayerA", gameId: "G2", score: 10 },
  { playerId: "PlayerB", gameId: "G2", score: 14 },
  { playerId: "PlayerC", gameId: "G2", score: 66 },
  { playerId: "PlayerD", gameId: "G2", score: 80 }
]);

How the Data Is Stored in MongoDB

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

mongosh
db.gamescores.find()

Read each document like this:

Field in MongoDBMeaningExample
playerIdWho played"PlayerD"
gameIdWhich game"G1"
scorePoints scored1
Picture
G1 scores: 99, 33, 31, 1
G2 scores: 80, 66, 14, 10

Next: $bottomN keeps the lowest 3 per game.

How It Works

  1. Insert once. The sample command adds 8 documents to gamescores.
  2. MongoDB stores fields as written. Each document keeps the same field names and values you inserted.
  3. find() shows the same shape. What you inserted is what you get back—ready for $bottomN in the next examples.

Example 2 — Three Lowest Scores in One Game

Goal: for game G1 only, return the three lowest scorers as [playerId, score] pairs.

mongosh
db.gamescores.aggregate([
  { $match: { gameId: "G1" } },
  {
    $group: {
      _id: "$gameId",
      bottomThree: {
        $bottomN: {
          n: 3,
          sortBy: { score: -1 },
          output: ["$playerId", "$score"]
        }
      }
    }
  }
]);

How It Works

  1. Filter first. $match keeps only G1 documents so the group is small and clear.
  2. One bucket. _id: "$gameId" makes a single group labeled "G1".
  3. Rank high to low. sortBy: { score: -1 } orders scores as 99 → 33 → 31 → 1.
  4. Take the end. n: 3 keeps the last three: 33, 31, and 1 (PlayerC’s 99 drops off).
  5. Shape each row. output: ["$playerId", "$score"] turns each kept document into a two-value array.

Example 3 — Bottom 3 per Game

Goal: same bottom-3 rule for every gameId, with a projection object instead of tuples.

mongosh
db.gamescores.aggregate([
  {
    $group: {
      _id: "$gameId",
      bottomThree: {
        $bottomN: {
          n: 3,
          sortBy: { score: -1 },
          output: { player: "$playerId", score: "$score" }
        }
      }
    }
  },
  { $sort: { _id: 1 } }
]);

How It Works

  1. Split by game. Each distinct gameId gets its own group and its own bottom-3 array.
  2. Same ranking rule. Descending score still means “lowest at the bottom” in every group.
  3. Friendly objects. output: { player, score } is easier to read in APIs than nested arrays.
  4. Stable report order. Final $sort on _id lists G1 before G2 (group order from $group alone is not guaranteed).

Example 4 — $bottomN vs $bottom

Same G1 filter and sortBy, two accumulators side by side: one worst player vs the worst three.

mongosh
db.gamescores.aggregate([
  { $match: { gameId: "G1" } },
  {
    $group: {
      _id: "$gameId",
      worstOnly: {
        $bottom: {
          sortBy: { score: -1 },
          output: ["$playerId", "$score"]
        }
      },
      worstThree: {
        $bottomN: {
          n: 3,
          sortBy: { score: -1 },
          output: ["$playerId", "$score"]
        }
      }
    }
  }
]);

How It Works

  1. Same sort, different size. Both use sortBy: { score: -1 }, so “bottom” means the same ranking.
  2. $bottom = one. worstOnly is a single value: PlayerD with score 1.
  3. $bottomN = many. worstThree is an array of three pairs ending at that same lowest score.
  4. Pick by need. Dashboards that show only the worst player use $bottom; bottom-k lists use $bottomN.

Edge Cases & Pitfalls

Check these before shipping a bottom-N report.

sortBy

“Bottom” follows sortBy

Lowest scores need descending sortBy. Ascending score makes the highest values sit at the bottom.

null

Nulls are kept

Missing fields become null and are not filtered out. With descending numeric sort they often land in the bottom slots.

ties

Break ties explicitly

Equal scores can swap places. Add a secondary key, e.g. sortBy: { score: -1, playerId: 1 }.

version

Needs MongoDB 5.2+

Older servers reject $bottomN. Confirm version before deploying.

Performance Notes

ApproachCostBest for
$bottomNIn-group ranking; keeps up to N docsBottom-k lists without a prior global sort
$sort + $group + sliceExtra stage; more memory on large setsLegacy workaround before 5.2
$minNValues only (no full document ranking)N smallest numbers when identity fields are unused

Keep n small and filter early with $match when you only need one game or category. Large groups are still subject to the aggregation memory limit (about 100 MB per group).

Key Takeaways

  • Role: array of the last N ranked documents inside $group.
  • Keys: always set n, sortBy, and output.
  • Lowest scores: use sortBy: { score: -1 } so lows sit at the bottom.
  • vs $bottom: one document vs an array of up to N.

One line: $bottomN returns the last N documents after sortBy—set ranking carefully, then shape each element with output.

Frequently Asked Questions

$bottomN returns an array of the N documents ranked at the bottom of each $group bucket according to sortBy. Use it for bottom-3 scorers, lowest-priced products, or earliest N orders when one record is not enough.
Inside $group: { bottomThree: { $bottomN: { n: 3, sortBy: { score: -1 }, output: { name: "$player", score: "$score" } } } }. All three keys—n, sortBy, and output—are required.
$bottom returns exactly one bottom-ranked document (a single object). $bottomN returns up to N documents as an array. Use $bottom for the single worst record; use $bottomN for a bottom-k list.
MongoDB ranks documents, then takes the last N in that order. For lowest numeric scores, use sortBy: { score: -1 } (descending)—high scores first, lowest scores at the bottom. Always match sortBy to your business definition of bottom.
$bottomN returns all documents in the group—never more than exist. If a class has only two students and n is 3, you get an array of two items.
$bottomN was added in MongoDB 5.2 alongside $top, $bottom, and $topN. On older servers, approximate with $sort + $group patterns (less efficient).

Next: $first

Return the first value encountered in each group.

$first 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