Skip to content

Split Loop

A single loop is often pressed into doing two different things at once — summing one thing while finding the maximum of another, or collecting names while counting matches. Split it into two loops, each iterating the same collection but doing exactly one job. Each loop now reads as a single idea, and you are free to refactor or move each one independently.

You read a loop and find yourself mentally untangling two threads: “this part builds the total… and this part, also in here, tracks the youngest person.” Two accumulators that have nothing to do with each other share a loop body only because they happen to walk the same list. That coupling makes the loop hard to name, hard to extract, and hard to change — touch the total logic and you risk the youngest-person logic.

A loop over employees that computes total salary and, unrelatedly, finds the youngest age. Two jobs, one loop. We split them.

// Before — one loop, two unrelated jobs
function report(people: Person[]): { total: number; youngest: number } {
let total = 0;
let youngest = people[0]?.age ?? Infinity;
for (const p of people) {
total += p.salary;
if (p.age < youngest) youngest = p.age;
}
return { total, youngest };
}
// After — each loop has a single purpose
function report(people: Person[]): { total: number; youngest: number } {
return { total: totalSalary(people), youngest: youngestAge(people) };
}
function totalSalary(people: Person[]): number {
let total = 0;
for (const p of people) total += p.salary;
return total;
}
function youngestAge(people: Person[]): number {
let youngest = people[0]?.age ?? Infinity;
for (const p of people) {
if (p.age < youngest) youngest = p.age;
}
return youngest;
}
  1. Copy the entire loop so you have two identical loops over the same collection.
  2. In the first loop, delete the statements that belong to the second job. In the second loop, delete the statements that belong to the first. Each loop is now single-purpose.
  3. Run your tests. Behaviour must be unchanged.
  4. Apply Extract Function to each loop so it becomes a well-named query like totalSalary or youngestAge.
  5. Run your tests again — the caller now reads as two named results instead of one tangled loop.

Split a loop whenever its body carries two responsibilities that you would describe with the word “and”. The payoff is clarity and reusability: each loop is easy to name, easy to extract, and easy to change in isolation — and Split Loop is usually the first step toward replacing each piece with a clear pipeline operation.

The obvious objection is performance: you now iterate twice. In nearly all real code that cost is negligible compared to the gain in readability, and a single iteration is a premature optimisation. If profiling later proves a hot loop matters, you can merge them back — but optimise from clean, well-named code, not from a tangle.

What problem does Split Loop solve?
What is the first mechanical step of Split Loop?
What is the common objection to Split Loop, and the usual answer?
Split Loop is typically followed by which refactoring on each resulting loop?