Lodash _.sumBy() method

Beginner
⏱️ 7 min read
📚 Updated: May 2026
🎯 3 Code examples
🚀 3 Try-it labs
Lodash

What you’ll learn

  • How _.sumBy is implemented as (array && array.length) ? baseSum(array, baseIteratee(iteratee, 2)) : 0—and what baseIteratee accepts.
  • All five iteratee shapes: function, property path string, dotted path, [key, value] pair, and matches object.
  • Why missing properties are silently skipped (no NaN)—and how that differs from _.meanBy.
  • How the string-concatenation trap from _.sum resurfaces when an iteratee returns a string.
  • The all-or-nothing BigInt rule, the friendly empty-input guard, and how booleans coerce when the iteratee is a predicate.

Prerequisites

Read _.sum() first—_.sumBy shares its baseSum backbone and inherits every quirk. _.meanBy() is the sibling to compare against for missing-field semantics.

  • Iteratee shorthand: any *By helper accepts a function, a string property path, a dotted path, a [key, value] tuple, or a matches object. baseIteratee picks the right shape.
  • JavaScript + coercion: a single string in the running total flips arithmetic to concatenation. Same trap as _.sum.

Overview

Two-liner: guard the array, then call baseSum(array, baseIteratee(iteratee, 2)). baseIteratee turns whatever you pass into a function—'total' becomes _.property('total'), 'price.base' becomes a nested getter, ['active', true] becomes _.matchesProperty, and a plain object becomes _.matches. baseSum then folds with native +, skipping any element whose iteratee returns undefined. That last detail is why missing properties produce a clean (if smaller) total instead of NaN.

Five iteratee shapes

Function, property path, dotted path, [k,v] tuple, matches object.

Missing ⇒ skipped

No NaN from absent properties—the row just doesn’t contribute.

String poisoning

If your iteratee returns a string, the whole sum becomes a concatenated string.

Syntax

javascript
_.sumBy(array, [iteratee=_.identity])
  • array (Array): the array to iterate over.
  • iteratee (Function | string | Array | Object): the iteratee invoked per element. Default _.identity.
  • Returns (number): the sum. 0 for empty, null, or undefined array. May be a string (concat) if any iteratee return is a string. May be NaN if any iteratee return is NaN.
1

All five iteratee shapes

The docs example plus the four shorthand forms. They’re interchangeable: pick whichever is clearest at the call site.

javascript
import sumBy from "lodash/sumBy";

const objects = [{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }];

// (a) function iteratee  -- docs example
console.log(sumBy(objects, o => o.n));   // 20

// (b) property-path string shorthand
console.log(sumBy(objects, "n"));          // 20

// (c) dotted path for nested objects
const carts = [
  { line: { price: 10 } },
  { line: { price: 25 } },
  { line: { price: 30 } }
];
console.log(sumBy(carts, "line.price"));   // 65

// (d) [key, value] -- matchesProperty (returns boolean -> coerces 1/0)
const tasks = [
  { active: true,  n: 1 },
  { active: false, n: 2 },
  { active: true,  n: 3 }
];
console.log(sumBy(tasks, ["active", true])); // 2  (counts matches)

// (e) matches object -- same idea, slightly nicer for multi-key
console.log(sumBy(tasks, { active: true }));  // 2

// No iteratee (or null) -- falls back to _.identity, behaves like _.sum
console.log(sumBy([1, 2, 3]));               // 6
console.log(sumBy([1, 2, 3], null));         // 6
Try it Yourself
2

Missing fields, null, NaN — what actually happens

A common bit of folklore says “_.sumBy returns NaN when a property is missing.” That’s wrong: baseSum skips elements whose iteratee returns undefined. Below shows the actual rules and how to recover the “missing-as-zero” behaviour if you want it.

javascript
import sumBy from "lodash/sumBy";

const invoices = [
  { id: 1, total: 100 },
  { id: 2 },             // total is missing -> undefined -> SKIPPED
  { id: 3, total: 50 }
];
console.log(sumBy(invoices, "total"));            // 150 (not NaN)

// null in a property coerces to 0 (counted)
console.log(sumBy([{ n: 1 }, { n: null }, { n: 3 }], "n")); // 4

// NaN poisons the entire result
console.log(sumBy([{ n: 1 }, { n: NaN }, { n: 3 }], "n"));  // NaN

// Want missing-as-zero? Use a function iteratee with ?? (or || for 0/null/undefined)
console.log(sumBy(invoices, r => r.total ?? 0));  // 150 -- same as skipping here

// Different result if you also want zeros from null:
const sparse = [
  { n: 1 },
  { n: null },
  {}
];
console.log(sumBy(sparse, "n"));                   // 1   (null -> 0 counted, undefined skipped)
console.log(sumBy(sparse, r => r.n ?? 0));         // 1
console.log(sumBy(sparse, r => r.n || 0));         // 1

// Friendly guard for missing arrays
console.log(sumBy([], "n"));                       // 0
console.log(sumBy(null, "n"));                     // 0
console.log(sumBy(undefined, "n"));                // 0
Try it Yourself
3

Realistic patterns — revenue, string-trap, BigInt

Three production-y cases: a derived-field rollup (qty × price), the string-trap you’ll hit with untyped data, and how BigInt behaves.

javascript
import sumBy from "lodash/sumBy";

// Revenue rollup: derive a value per row
const sales = [
  { product: "phone",  revenue: 800,  unitsSold: 2 },
  { product: "tablet", revenue: 500,  unitsSold: 1 },
  { product: "laptop", revenue: 1200, unitsSold: 3 }
];
console.log(sumBy(sales, o => o.revenue * o.unitsSold)); // 5700

// String-trap: untyped data (e.g. parsed from a CSV) keeps prices as strings
const items = [
  { name: "A", price: "5" },
  { name: "B", price: "10" },
  { name: "C", price: "7" }
];
console.log(sumBy(items, "price"));                        // "5107"   (a string!)
console.log(typeof sumBy(items, "price"));                 // "string"

// Fix: coerce inside the iteratee
console.log(sumBy(items, o => Number(o.price)));           // 22

// BigInt: all-or-nothing
console.log(sumBy([{ n: 1n }, { n: 2n }, { n: 3n }], "n")); // 6n

try {
  sumBy([{ n: 1n }, { n: 2 }], "n");
} catch (err) {
  console.log("Threw:", err.message);                       // "Cannot mix BigInt and other types..."
}

// Array-likes (anything with a length) work too
const arrLike = { length: 3, 0: { n: 10 }, 1: { n: 20 }, 2: { n: 30 } };
console.log(sumBy(arrLike, "n"));                           // 60
Try it Yourself

📋 _.sumBy vs _.sum, _.meanBy, and reduce

Scenario_.sumBy_.sum_.meanByNotes
Array of objects, sum a key_.sumBy(rows, "n")requires rows.map(r => r.n)_.meanBy(rows, "n")_.sumBy avoids the intermediate array.
Missing field on a rowskipped, no NaNn/a (uses raw values)skipped from sum but counted in divisor (silently lowers mean)The most important difference between _.sumBy and _.meanBy.
Empty / null array00NaNOnly _.meanBy lacks the friendly guard (because of the divisor).
Iteratee returns a stringconcat → stringconcat → stringconcat → string / NaNSame root cause: native + coercion.
BigInt in every row6n6nthrows (BigInt ÷ Number)_.meanBy can’t divide a BigInt by a Number.
Predicate iteratee (count matches)_.sumBy(rows, ["k", v]) works (true→1)n/aworks but rarely meaningfulBooleans coerce to 1 / 0.

Pitfalls to avoid

Old docs

“Missing returns NaN” is wrong

Several copies of the legacy reference claim missing properties produce NaN. They don’t—baseSum’s explicit undefined guard skips them. If you actually want NaN on missing data, use _.sumBy(rows, r => r.n) with r.n undefined deliberately converted to NaN (e.g. +r.n).

Strings

Untyped numeric data poisons the sum

CSV/JSON parsing often leaves numbers as strings. _.sumBy(items, "price") over {price: "5"} rows returns a concatenated string. Use a function iteratee with Number() to be safe.

No second arg

Falls back to identity

_.sumBy(objects) with no iteratee behaves like _.sum(objects)—adding raw objects with + gives the useless string "[object Object][object Object]". Always pass an iteratee for object arrays.

BigInt

All-or-nothing

Every row must be a BigInt, or the iteratee must coerce to BigInt. Mixing throws.

Predicates

[key, value] sums booleans

_.sumBy(rows, ["active", true]) counts matching rows (true→1, false→0). To sum a numeric field on matching rows, filter first: _.sumBy(rows.filter({active: true}), "price").

❓ FAQ

_.sum(array) adds the raw elements. _.sumBy(array, iteratee) runs each element through baseIteratee first — that can be a function, a property-path string ('total'), a dotted path ('price.base'), an [key, value] tuple, or a matches object. With no iteratee (or null), _.sumBy falls back to identity and behaves exactly like _.sum.
baseSum has an explicit `if (current !== undefined)` guard. A missing property like `{}.price` returns undefined, so that row is skipped — the running total moves on with the next element. The result is the sum of the rows that DID have the property. If you want missing fields to count as 0 instead, use `obj => obj.price || 0` or `obj => obj.price ?? 0` as the iteratee.
Because at least one iteratee return was a string. baseSum uses native +, so as soon as a string enters the running total, every subsequent + becomes concatenation. Coerce inside the iteratee: `_.sumBy(rows, r => Number(r.price))`. Be aware that Number('oops') is NaN, which then poisons the sum.
Yes. _.sumBy(rows, ['active', true]) sums the iteratee's TRUE/FALSE result (booleans coerce: true → 1, false → 0), so it counts matching rows. _.sumBy(rows, { active: true }) does the same — it's just baseIteratee resolving to a predicate. For aggregating a numeric field on matching rows, write the function explicitly: `rows.filter({active: true}).reduce(...)` or `_.sumBy(rows.filter({active: true}), 'price')`.
Yes when EVERY iteratee return is a BigInt (all-or-nothing). _.sumBy([{n:1n},{n:2n},{n:3n}], 'n') returns 6n. Mixing BigInt with Number throws Cannot mix BigInt and other types — same as native +.
Big difference. _.sumBy skips missing-field rows AND has no divisor — the result is mathematically the sum of what's there, no bias. _.meanBy skips missing-field rows from the sum but STILL divides by array.length, which silently lowers the average. If you care about the average over rows that actually had the field, do _.sumBy(rows, 'n') / rows.filter(r => r.n != null).length.

Summary

  • Purpose: sum each element’s iteratee value—baseSum(array, baseIteratee(iteratee, 2)) with the same empty-array guard as _.sum.
  • Remember: five iteratee shapes; missing → skipped (no NaN); null0; one stringy return poisons to concat; NaN poisons; BigInt all-or-nothing; empty/null array → 0.
  • Next: back to Lodash Math methods, or read the official Lodash docs for _.sumBy.
Did you know?

_.sumBy([{price:1}, {}, {price:2}], 'price') returns 3, not NaN. baseSum skips any element whose iteratee returns undefined—and because there’s no divisor (unlike _.meanBy), missing fields are silently dropped without biasing the result. The flip side: a single stringy property value flips + to concatenation, so _.sumBy([{x:'5'}, {x:'10'}], 'x') returns the string "510".

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.

6 people found this page helpful