MongoDB $accumulator Operator

Intermediate
⏱️ 11 min read
📚 Updated: Jul 2026
🎯 5 Examples
Aggregation

What You’ll Learn

The $accumulator operator lets you write custom JavaScript inside $group when built-in accumulators like $sum or $avg are not enough—ideal for conditional totals, weighted math, or bespoke rollups.

01

Custom logic

Your own JS functions.

02

init + accumulate

Start state, update per doc.

03

merge

Combine parallel partial states.

04

finalize

Optional output transform.

05

vs $sum

Custom vs built-in speed.

06

lang: "js"

JavaScript required today.

Definition and Usage

$accumulator is a MongoDB accumulator used in the $group stage. Instead of a fixed operation like addition or averaging, you supply JavaScript functions that define how state is initialized, updated for each document, merged across workers, and optionally finalized.

💡
Beginner tip

Think of $accumulator as a DIY reducer. init sets the starting value (like 0 for a sum). accumulate runs once per document and returns the new state. When MongoDB splits work across shards, merge combines two partial states into one.

Reach for built-in accumulators first—$sum, $avg, $push. Use $accumulator when your business rule needs JavaScript expressiveness that those operators cannot provide.

📝 Syntax

$accumulator takes an object with JavaScript function definitions:

mongosh
{
  $group: {
    _id: <expression>,
    <outputField>: {
      $accumulator: {
        init: <function>,
        accumulate: <function>,
        accumulateArgs: <array>,
        merge: <function>,
        finalize: <function>,
        lang: <string>
      }
    }
  }
}

Syntax Rules

  • init — zero-argument function; returns the initial accumulator state for each group.
  • accumulate — function (state, ...args); called once per document; must return updated state.
  • accumulateArgs — array of expressions (usually field paths) passed as extra arguments after state.
  • merge — function (state1, state2); combines partial states from parallel processing.
  • finalize — optional; function (state); transforms state before output.
  • lang — required; must be "js" (JavaScript).
mongosh
db.transactions.aggregate([
  {
    $group: {
      _id: null,
      totalAmount: {
        $accumulator: {
          init: function() { return 0; },
          accumulate: function(state, amount) {
            return state + amount;
          },
          accumulateArgs: ["$amount"],
          merge: function(s1, s2) { return s1 + s2; },
          lang: "js"
        }
      }
    }
  }
]);

⚡ Quick Reference

QuestionAnswer
Stage$group accumulator
Required keysinit, accumulate, accumulateArgs, merge, lang
Optional keyfinalize
Languagelang: "js"
When to useLogic that built-in accumulators cannot express
Prefer instead$sum, $avg, $push when they fit
init
init: function() {
  return 0;
}

Starting state per group

accumulate
accumulate: function(
  state, amount
) {
  return state + amount;
}

Update per document

merge
merge: function(s1, s2) {
  return s1 + s2;
}

Combine partial states

finalize
finalize: function(state) {
  return state;
}

Optional last step

🧰 Parameters

Each property in the $accumulator object plays a distinct role in the aggregation lifecycle:

init Required

Called once when a group starts. Return the initial state— a number, object, or array depending on your logic.

init: function() { return { sum: 0, count: 0 }; }
accumulate Required

First parameter is always the current state. Additional parameters come from accumulateArgs, in order.

accumulate: function(state, amt) {
  return state + amt;
}
accumulateArgs Required

Array of aggregation expressions evaluated per document and passed to accumulate after state.

accumulateArgs: ["$amount"]
accumulateArgs: ["$price", "$qty"]
merge Required

Combines two states of the same shape. Must be associative with your accumulate logic so sharded results stay correct.

merge: function(a, b) {
  return { sum: a.sum + b.sum, count: a.count + b.count };
}
finalize Optional

Runs once after all documents in a group are processed. Return the value that appears in the output document.

finalize: function(s) {
  return s.count ? s.sum / s.count : null;
}
lang Required

Specifies the language for all functions. Currently only "js" is supported.

lang: "js"

Examples Gallery

Transaction amounts—build a custom running total, compute average with internal state, and compare with native $sum.

📚 Getting Started

Sample transactions and a minimal custom sum using $accumulator.

Example 1 — Sample transactions collection

mongosh
db.transactions.insertMany([
  { amount: 100, category: "food" },
  { amount: 150, category: "food" },
  { amount: 200, category: "travel" },
  { amount:  50, category: "food" }
]);

How It Works

Four transactions—three in food (100 + 150 + 50 = 300) and one in travel (200). Good data for per-category and global totals.

Example 2 — Custom total with $accumulator

mongosh
db.transactions.aggregate([
  {
    $group: {
      _id: null,
      totalAmount: {
        $accumulator: {
          init: function() { return 0; },
          accumulate: function(state, amount) {
            return state + amount;
          },
          accumulateArgs: ["$amount"],
          merge: function(s1, s2) { return s1 + s2; },
          finalize: function(state) { return state; },
          lang: "js"
        }
      }
    }
  }
]);

/* Result:
[ { _id: null, totalAmount: 500 } ]
  100 + 150 + 200 + 50 = 500 */
*/

How It Works

init starts at 0. Each document adds its amount via accumulate. merge adds partial totals if MongoDB parallelizes the group.

📈 Practical Patterns

Custom averages, built-in comparisons, and multi-field logic.

Example 3 — Custom average with internal state

Track both sum and count in state, then divide in finalize:

mongosh
db.transactions.aggregate([
  {
    $group: {
      _id: "$category",
      avgAmount: {
        $accumulator: {
          init: function() {
            return { sum: 0, count: 0 };
          },
          accumulate: function(state, amount) {
            return {
              sum: state.sum + amount,
              count: state.count + 1
            };
          },
          accumulateArgs: ["$amount"],
          merge: function(a, b) {
            return { sum: a.sum + b.sum, count: a.count + b.count };
          },
          finalize: function(state) {
            return state.count === 0
              ? null
              : state.sum / state.count;
          },
          lang: "js"
        }
      }
    }
  },
  { $sort: { _id: 1 } }
]);

/* food   → avgAmount: 100  (300 / 3)
   travel → avgAmount: 200  (200 / 1) */
*/

How It Works

State holds intermediate values; finalize produces the user-facing average. Built-in $avg does this natively—this pattern shows how state objects work.

Example 4 — $accumulator vs built-in $sum

mongosh
db.transactions.aggregate([
  {
    $group: {
      _id: null,
      customTotal: {
        $accumulator: {
          init: function() { return 0; },
          accumulate: function(s, a) { return s + a; },
          accumulateArgs: ["$amount"],
          merge: function(s1, s2) { return s1 + s2; },
          lang: "js"
        }
      },
      nativeTotal: { $sum: "$amount" }
    }
  }
]);

/* Both return 500 — use $sum for simple addition */
*/

How It Works

When both produce the same result, prefer $sum—less code, better performance, no JavaScript sandbox overhead.

Example 5 — Conditional accumulate (skip small amounts)

Only add amounts above 75—a rule that needs custom logic:

mongosh
db.transactions.aggregate([
  {
    $group: {
      _id: null,
      bigSpendsOnly: {
        $accumulator: {
          init: function() { return 0; },
          accumulate: function(state, amount) {
            if (amount > 75) {
              return state + amount;
            }
            return state;
          },
          accumulateArgs: ["$amount"],
          merge: function(s1, s2) { return s1 + s2; },
          lang: "js"
        }
      }
    }
  }
]);

/* Skips 50; sums 100 + 150 + 200 = 450 */
*/

How It Works

Conditional logic inside accumulate is a common reason to choose $accumulator. You could also use $sum with a $cond expression—compare both approaches for readability.

🧠 How $accumulator Works

1

init runs once per group

MongoDB calls init() when a new _id bucket starts and stores the returned state.

Init
2

accumulate per document

For each document, evaluate accumulateArgs, then call accumulate(state, ...args).

Loop
3

merge partial states

If work is split across processes, merge folds partial states into one combined state.

Merge
=

finalize & output

Optional finalize(state) shapes the final value written to the group result document.

📝 Notes

  • $accumulator runs JavaScript—expect higher overhead than native accumulators like $sum.
  • merge is always required even on a standalone server; it defines how partial states combine.
  • lang: "js" is mandatory; functions must be valid JavaScript.
  • State can be any BSON-serializable value—numbers, objects, or arrays.
  • Prefer expression-based accumulators ($sum with $cond) when they cover your rule without custom code.
  • Previous topic: Accumulators hub. Next: $addToSet.

Conclusion

$accumulator is MongoDB’s escape hatch for custom group-level logic: you own initialization, per-document updates, merging, and optional final formatting through JavaScript.

Start with built-in accumulators whenever possible. When you truly need bespoke rollups, wire up init, accumulate, and merge carefully—then continue with $addToSet for unique value collection.

💡 Best Practices

✅ Do

  • Try built-in accumulators and expression operators before writing custom JavaScript
  • Keep accumulate pure—return new state, avoid mutating external variables
  • Design merge to match how accumulate combines values
  • Use finalize only for output shaping, not heavy per-document work
  • Test on a small dataset first—debugging JS inside aggregation is harder than plain mongosh scripts

❌ Don’t

  • Replace simple $sum or $avg with $accumulator without a clear benefit
  • Forget merge—pipelines fail or return wrong results on sharded clusters without it
  • Store non-serializable values in state (functions, closures to outer scope)
  • Perform slow I/O or network calls inside accumulator functions
  • Assume finalize is required—it is optional

Key Takeaways

Knowledge Unlocked

Five things to remember about $accumulator

Use these points when built-in accumulators are not enough.

5
Core concepts
🚀 02

init

Starting state.

Lifecycle
🔄 03

accumulate

Per-document update.

Core
🔗 04

merge

Combine partial states.

Sharding
05

Prefer built-ins

$sum when possible.

Performance

❓ Frequently Asked Questions

$accumulator lets you define custom JavaScript functions inside $group to compute a value across documents. You control initialization (init), per-document updates (accumulate), combining partial results (merge), and optional final formatting (finalize).
Use it when built-in accumulators like $sum, $avg, or $push cannot express your logic—weighted averages, conditional totals, or multi-field custom rollups. Prefer built-ins when they fit; they are faster and simpler.
Inside the $group stage as an accumulator: { customTotal: { $accumulator: { init: function() { … }, accumulate: function(state, arg) { … }, accumulateArgs: ["$field"], merge: function(a, b) { … }, lang: "js" } } }.
merge combines accumulator state from parallel workers during sharded aggregation. Even on a single node you must provide merge—it defines how two partial states become one.
No. finalize is optional. Use it when the raw accumulated state needs a last transformation before output—for example converting an internal object into a single number.
$sum adds numeric field values with native code. $accumulator runs JavaScript you write, so it can implement any logic—but with higher overhead. Use $sum for simple totals.
Did you know?

The same custom-sum logic in $accumulator can often be written as { $sum: "$amount" } or { $sum: { $cond: [condition, "$amount", 0] } } without JavaScript—native operators compile to faster server code. Reserve $accumulator for state shapes and rules that expressions cannot express cleanly. See the official $accumulator docs.

Continue the Accumulators Series

Custom logic with $accumulator, then learn $addToSet for unique value arrays.

Next: $addToSet →

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