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.
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);
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.
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.
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.
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.