JAVASCRIPT / LOOPS
Accumulator patterns: sums, counts, and collections
Build sums, conditional counts, and array or object collections with a single accumulator variable seeded correctly and updated once per iteration.
What you will learn
- Declare the accumulator outside the loop so each pass builds on the previous value
- Seed with the operation's identity: 0 for sums, 1 for products, [] for collections
- Turn a sum into a count by guarding count += 1 with an if on the current item
- Update several accumulators in one pass instead of looping the same array three times
Understanding Accumulator patterns: sums, counts, and collections
An accumulator is a variable whose only job is to remember the result so far. It must be declared before the loop starts, because each iteration reads what the previous iteration left behind and writes a new value back; a `let total = 0` placed inside the body would be a brand-new variable on every pass. The body then holds one small rule per accumulator: given what is already stored and the current item, what should be stored next. Seen that way, `total += t`, `count += 1`, and `list.push(t)` are the same pattern with different combining steps.
The starting value is not arbitrary. Pick the value that is already the correct answer for zero items: 0 for a sum, 1 for a product, '' for glued-together text, [] for a collection, {} for a tally, -Infinity for a running maximum. A wrong seed corrupts results quietly instead of crashing, because seeding a product with 0 pins it at 0 forever and seeding a maximum with 0 hides every negative value. Leaving the initializer off is worse still, since `undefined + 1` is NaN and NaN survives every later addition and comparison.
Container accumulators behave differently from numeric ones. `const list = []` is fine because `push` mutates the array the binding already points at, while a numeric accumulator needs `let` since `total += t` rebinds the variable. A counter is just a sum with a guard: put `count += 1` inside an `if` and you are counting matches rather than totalling values. Because each accumulator is independent state, several of them can share one pass over the data, which costs less than three loops and guarantees they all describe the same snapshot.
const temps = [18, 21, 15, 24, 19, 27, 22];
let total = 0; // sum accumulator: 0 is the additive identity
let warmDays = 0; // counter: only some items contribute
const warm = []; // collection accumulator: starts empty
for (const t of temps) {
total += t;
if (t >= 22) {
warmDays += 1;
warm.push(t);
}
}
console.log('total:', total);
console.log('average:', (total / temps.length).toFixed(1));
console.log('warm days:', warmDays);
console.log('warm temps:', warm.join(', '));An accumulator is one variable that lives outside the loop, starts at the identity value for its operation, and is rewritten by every iteration.
Worked examples
The accumulator that keeps resetting
Shows why the accumulator has to be declared in the scope around the loop, not in its body.
const prices = [4, 7, 2];
for (const p of prices) {
let total = 0; // fresh variable on every pass
total += p;
console.log('inside:', total);
}
let grandTotal = 0; // one variable for the whole loop
for (const p of prices) {
grandTotal += p;
console.log('running:', grandTotal);
}
console.log('final:', grandTotal);Example explained
Line 1`let total = 0` sits inside the block, so each pass creates a new binding and discards the previous one.
Line 2The inside logs echo the items instead of a total, and `total` cannot be read at all once the loop ends.
Line 3`grandTotal` is declared in the enclosing scope, so `+=` can read what the previous pass wrote.
Line 4The running logs expose the accumulator's intermediate states: 4, then 11, then 13.
Counting into an object
Accumulates one counter per distinct value using an object instead of separate variables.
const votes = ['yes', 'no', 'yes', 'abstain', 'yes'];
const tally = {};
for (const v of votes) {
tally[v] = (tally[v] || 0) + 1;
}
for (const key of Object.keys(tally)) {
console.log(key, tally[key]);
}Example explained
Line 1`const tally = {}` is the identity for tallying: no keys means nothing counted yet.
Line 2`tally[v]` is `undefined` the first time a value appears, and `undefined + 1` is NaN, so `|| 0` supplies the missing start value.
Line 3`tally` can stay `const` because every iteration writes a property rather than rebinding the variable.
Line 4Plain string keys are listed in first-seen order, which is why `yes` prints before `no`.
Seeds that are not zero
Demonstrates the multiplicative identity and why 0 is an unsafe seed for a running maximum.
const factors = [3, 5, 2];
let product = 1; // 1 leaves a product unchanged
for (const f of factors) product *= f;
const balances = [-40, -15, -90];
let maxZero = 0; // wrong seed for negative data
let maxSafe = -Infinity; // loses to any real number
for (const b of balances) {
if (b > maxZero) maxZero = b;
if (b > maxSafe) maxSafe = b;
}
console.log('product:', product);
console.log('max seeded with 0:', maxZero);
console.log('max seeded with -Infinity:', maxSafe);Example explained
Line 1`product` starts at 1 because multiplying by 1 changes nothing; a seed of 0 would trap the result at 0 forever.
Line 2`maxZero` starts at 0, which is bigger than every balance, so no comparison ever succeeds and the seed itself is printed.
Line 3`maxSafe` starts at -Infinity, so the first real value always wins the comparison and replaces it.
Line 4One pass drives both comparisons: accumulators hold separate state and can end up disagreeing.
Important notes
`const` on an array or object accumulator blocks reassignment only; `push` and property writes still work, so use `let` only when the update replaces the whole value.
Order matters when the operation is not commutative: string building and `push` bake iteration order into the result, and even float sums can differ in their last digits if added in another order.
Common mistakes
Declaring the accumulator inside the loop body: it resets on every pass, so the result reflects only the last item and the variable does not exist after the loop.
Accumulating values straight from input fields without converting them: `total` becomes a string and `total += '5'` concatenates, producing '05' then '0512' instead of a number.
Writing `let count;` with no seed: the first `count += 1` evaluates `undefined + 1` as NaN, and later checks such as `count > 0` are then false.
Try it yourself
Change, predict, then run
In the browser console, loop once over ['loop', 'seed', 'accumulate', 'sum'] and build three accumulators: the total number of characters, how many words are longer than four letters, and an array of first letters. Log all three after the loop ends.
Open the JavaScript workspaceCheck your understanding
A working sum starts with `let total = 0;` and adds every element of arr. Someone changes the seed to `let total = arr[0];` but leaves the loop visiting every element. What is the effect?
- Nothing changes; the loop visits the same elements and the total is identical.
- It throws a TypeError as soon as the array is empty.
- The first element is added twice, and an empty array leaves total as undefined instead of 0.
- The total ends up one element short because the loop now skips arr[0].
Show answer
Seeding from the data instead of the identity counts arr[0] twice unless the loop also starts at index 1, and the total for no items should be 0, not undefined. Option 2 (the TypeError) is tempting because reading past the end of an array feels like an error, but arr[0] on an empty array simply evaluates to undefined and nothing throws.