Skip to content

Replace Loop with Pipeline

A loop that walks a collection, skips some elements, transforms others, and accumulates a result is doing several jobs braided together. A collection pipeline untangles them: each operation — filter, map, reduce — becomes a separate, named stage, and the data flows through them in order. The reader follows the data instead of tracking a mutable accumulator and an index. The behaviour is identical; the shape changes from “how to iterate” to “what each step does”.

You see a for loop with a result variable declared just above it, a continue or guarding if inside, and a transformation buried in the middle. To understand it you must mentally execute it, holding the accumulator’s running state in your head. The loop conflates selecting which elements count, transforming them, and combining them — three distinct ideas with no visible seam.

Collect the names of active premium customers from a region. Imperatively this is one loop with a guard, a field access, and an append; as a pipeline it is filter, then filter, then map.

// Before
function premiumNames(customers: Customer[], region: string): string[] {
const names: string[] = [];
for (const c of customers) {
if (c.region !== region) continue;
if (!c.isPremium) continue;
names.push(c.name.toUpperCase());
}
return names;
}
// After
function premiumNames(customers: Customer[], region: string): string[] {
return customers
.filter((c) => c.region === region)
.filter((c) => c.isPremium)
.map((c) => c.name.toUpperCase());
}
  1. Find the collection the loop walks and identify the result it builds.
  2. Translate each piece of loop logic into a stage. A continue-style guard becomes a filter; a per-element transformation becomes a map; the accumulation of a single value becomes a reduce (or sum, join, and friends).
  3. Build the pipeline one stage at a time, leaving the loop in place beside it if that helps you compare.
  4. Replace the loop with the finished pipeline and return its result.
  5. Run your tests. The output must match the loop exactly, including for an empty input.
  6. In a language without built-in pipelines, such as Go, do not force one: keep a single loop but lift each condition into a clearly named boolean so the steps still read as distinct ideas.

Reach for a pipeline when a loop mixes filtering, transforming, and accumulating — the stages make each intention explicit and let you read the data’s journey from top to bottom. It shines when the operations are pure and order-independent.

The trade-offs: a loop with early break, an index that matters, or side effects in the body can be awkward or misleading as a pipeline, and a very long chain can become its own kind of puzzle. Some pipelines also allocate intermediate collections. Use it where it clarifies, and remember Go’s note above — a named-step loop is the idiomatic equivalent there.

What does Replace Loop with Pipeline make explicit?
A guarding "continue" inside the loop usually becomes which pipeline stage?
How does the lesson suggest handling this in Go, which has no built-in pipeline?
When is a loop a poor candidate for a pipeline?