Move Accumulation To Collecting Parameter

Symptom

A recursive or iterative traversal produces a result by building intermediate values at each level and merging them. The merge logic duplicates through the recursion; large traversals allocate and copy many short-lived intermediate collections; the algorithm's shape is obscured by the accumulate-and-merge bookkeeping.

Goal

One mutable accumulator (the collecting parameter) is threaded through the traversal. Each step appends to it directly; the caller reads the finished accumulator. Bookkeeping shrinks; the algorithm's intent reads cleanly.

Before the refactoring

// Each node builds a fragment and the parent concatenates.
// The accumulate-then-merge step is duplicated through the recursion.
class Section {
constructor(title) {
this.title = title;
this.children = [];
}
toLines() {
let lines = [this.title];
for (const child of this.children) {
lines = lines.concat(child.toLines().map((line) => ' ' + line));
}
return lines;
}
}
const output = root.toLines();

After the refactoring

// One collecting parameter; each node writes directly into it.
class Section {
constructor(title) {
this.title = title;
this.children = [];
}
printTo(lines, indent = '') {
lines.push(indent + this.title);
for (const child of this.children) {
child.printTo(lines, indent + ' ');
}
}
}
const output = [];
root.printTo(output);
Example source: Illustrative example written for this site, faithful to Kerievsky's pattern shape in Refactoring to Patterns (Addison-Wesley, 2004), chapter 8. The book uses a recursive tag-printing example with a StringBuffer collecting parameter; this JavaScript version uses a Section composite collecting lines into an array — same pattern, idiomatic JS host.
Pressure

Per-step intermediate collections create allocation churn and memory pressure on deep recursions. Merge code duplicates per node; subtle inconsistencies between merges (one method preserves order, another doesn't) silently produce wrong results.

Tradeoff

Mutable accumulators violate Separate Query from Modifier — methods that look like queries now mutate their arguments. The mutability must be tightly scoped; leaking the accumulator outside the traversal creates aliasing risks. Functional reasoning costs go up.

Relief

One pass, one allocation, one read at the end. The recursive shape reads as 'do my part, recurse for children'; the algorithm's structure becomes visible because the bookkeeping moves out of the body.

Trap

A collecting parameter that escapes the traversal becomes a shared mutable hand-off that downstream code must reason about. The pattern pays inside a bounded recursion; outside, it leaks the kind of state that Functional Core / Imperative Shell is meant to keep at the edge.