Clean Code Secrets: Push Ifs Up, Pull Fors Down Like a Pro

1 day ago 3
26 May 20253 min read

 Push Ifs Up, Pull Fors Down Like a Pro

Let’s discuss control flow. We’ll be covering three powerful techniques for faster, cleaner, and more maintainable code:

  • Push if statements up to centralize logic
  • Move for loops to the bottom to support batching and optimization
  • Move switch logic outside of hot loops.

We'll also examine the Big O consequences and how compilers benefit from clean structure.

Move if Statements to the Top

If you're verifying nulls or guards within a function, ask yourself: should this logic be at the calling site instead?

Bad:

1 interface User {

2 email: string;

3 }

4

5 function processUser(user: User | null): void {

6 if (!user) return;

7 sendEmail(user.email);

8 }

Good:

1 function processUser(user: User): void {

2 sendEmail(user.email);

3 }

4

5 const maybeUser = getUser();

6 if (maybeUser !== null) {

7 processUser(maybeUser);

8 }

Why?

  • Fewer branches within reusable logic
  • Call site has context to inform behavior
  • Enhances type safety and compiler inference

Big O:

Eliminating inner function branching decreases decisions at run-time from O(n) to O(1) per invocation in performance-critical code paths

Pull Down for Loops

Rather than looping externally, bring iteration into the function to take advantage of batching.

Bad:

1 for (let task of taskList) {

2 runTask(task);

3 }

Good:

1 function runTasks(tasks: Task[]): void {

2 for (let task of tasks) {

3 runTask(task);

4 }

5 }

6

7 runTasks(taskList);

Better:

1 function runTasksOptimized(tasks: Task[]): void {

2

3 for (let task of tasks) validate(task);

4

5

6 for (let task of tasks) execute(task);

7

8

9 for (let task of tasks) console.log(task);

10 }

Why?

  • Batch-aware logic minimizes the overhead of calls
  • Facilitates vectorization, caching, and stages of the pipeline

Big O:

O(n) remains O(n) but with less branching = better caching and better CPU efficiency

Combine: if Outside, for Inside

Bad:

1 for (let msg of messages) {

2 if (urgent) handleUrgent(msg);

3 else handleNormal(msg);

4 }

Good:

1 if (urgent) {

2 for (let msg of messages) handleUrgent(msg);

3 } else {

4 for (let msg of messages) handleNormal(msg);

5 }

Why?

  • Conditional extracted outside the loop
  • No repeated branching, assists CPU prediction

Big O:

  • Same number of steps overall, but reduced branch misprediction and improved runtime

Loop-Switching: Refactor Your Dispatch

Typical Code:

1 for (let item of items) {

2 switch (item.kind) {

3 case 'text': renderText(item); break;

4 case 'image': renderImage(item); break;

5 }

6 }

Improved:

1 const textItems = items.filter((i) => i.kind === 'text');

2 const imageItems = items.filter((i) => i.kind === 'image');

3

4 for (let t of textItems) renderText(t);

5 for (let i of imageItems) renderImage(i);

Why?

  • Fewer branches in the inner loop
  • Supports multi-pass or parallel strategies

Big O:

Minor increment (2×O(n)), but enhanced locality and optimizable dispatching

Compile-Time Wins: What Compilers Understand

  • Inner loops without branches allow compilers to employ SIMD and loop unrolling
  • Split loops can be parallelized or pipelined
  • Centralized conditions result in fewer jumps and improved CPU branch prediction

Final Thoughts

These aren't tricks. These are coding models for what works with the machine, not against it.

  • Organize information more effectively:
  • Shun accidental complexity
  • Improve performance (particularly in hot paths)
  • Make debugging and testing simpler

Push your ifs to the top, bring your fors to the bottom, shift your switch aside—and code to please the compiler.

TL;DR (Too Long? Don’t Refactor):

Read Entire Article