reduce
Fold a list into one value: a number, an object, or a new list.
reduce walks the list carrying an accumulator. Your function receives the accumulator and the current item and returns the next accumulator. Whatever the last call returns is the result.
Pass the initial value as the second argument. It sets the type of the result and makes an empty list safe: with no initial value, reducing an empty array throws. The one exception is picking one of the items themselves, like the best attempt; then guard the empty list first.
It is the general tool. Sums, counts by key, group by key, pick the best item, build a lookup table: all of them are the same loop with a different starting value and a different update.
Shape
const result = items.reduce((acc, item) => nextAcc, initialValue); Patterns
-
results.reduce((sum, r) => sum + r.points, 0)Sum a field. Start at 0.
-
items.reduce((counts, it) => { counts[it.status] = (counts[it.status] ?? 0) + 1; return counts; }, {})Count by key. Start with an empty object and return it every time.
-
items.reduce((groups, it) => { (groups[it.topic] ??= []).push(it.id); return groups; }, {})Group by key. ??= creates the bucket the first time a key shows up.
-
attempts.reduce((best, a) => (a.percent > best.percent ? a : best))Pick the best. No initial value here, so guard against an empty list first.
Pitfalls
- Forgetting to
returnthe accumulator from a block body. The next call receivesundefined. - Rebuilding the accumulator with spread on every step,
({ ...acc, [k]: v }), is quadratic. Mutating the accumulator you created and returning it is fine. - If a
maporfiltersays it more clearly, use that. Reduce earns its place when the output is a different shape than the input.
Exercises
Write the function, run it against the hidden cases. Cmd or Ctrl + Enter runs, Tab indents, Esc then Tab leaves the editor. 0 / 6 solved.
1. Total points
Return the sum of points across all results. An empty list totals 0.
totalPoints([{ points: 1 }, { points: 4 }, { points: 2 }]) → 7 2. Earned, rounded
Each result has an earned field. Return the sum of earned across all results, rounded to 2 decimal places, as a number (0.6, not the string "0.60"). An empty list totals 0.
pointsEarned([{ earned: 0.1 }, { earned: 0.2 }, { earned: 0.3 }]) → 0.6 3. Count by status
Return an object counting how many submissions have each status.
countByStatus(
[
{ status: "passed" },
{ status: "failed" },
{ status: "passed" }
]
) → { passed: 2, failed: 1 } 4. Group ids by topic
Return an object mapping each topic to the list of question ids with that topic, in input order.
groupByTopic(
[
{ id: "q3", topic: "vitals" },
{ id: "q2", topic: "safety" },
{ id: "q1", topic: "vitals" }
]
) → { vitals: ["q3", "q1"], safety: ["q2"] } 5. Best attempt
Return the attempt with the highest percent. On a tie, keep the one that comes first in the list. Return null for an empty list.
bestAttempt(
[
{ id: "a", percent: 50 },
{ id: "b", percent: 100 },
{ id: "c", percent: 100 }
]
) → { id: "b", percent: 100 } 6. Lookup by id
Return an object keyed by each document's id, with the document as the value.
byId([{ id: "d1", title: "A" }, { id: "d2", title: "B" }]) → { d1: { id: "d1", title: "A" }, d2: { id: "d2", title: "B" } } Ask a question about a list without building a new one.