Master Clean Code Architecture: Advanced Techniques for Professional Developers
26 May 20253 min read
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 }45 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 }45 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 }67 runTasks(taskList);
Better:
1 function runTasksOptimized(tasks: Task[]): void {2 // Stage 1: Validate3 for (let task of tasks) validate(task);45 // Stage 2: Execute6 for (let task of tasks) execute(task);78 // Stage 3: Cleanup9 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');34 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
if
s to the top, bring yourfor
s to the bottom, shift yourswitch
aside—and code to please the compiler.