The Art of Flattening: How to NOT Nest Your Code
Xiahua Liu May 10, 2024 #C++At the very beginning of the Linux kernel coding style document, it bluntly states:
if you need more than 3 levels of indentation, you’re screwed anyway, and should fix your program.
Code nesting—often called "Arrow Code" because the indentation shapes look like an arrow pointing to the right—exists in almost all programming languages. While indentation is necessary for structure, deep nesting dramatically increases cognitive load.
Have you ever stared at a block of code indented five tabs deep and wondered how to flatten it? This article explores design patterns and techniques specifically tailored to avoid nesting in C++.
Table-Driven Methods
Sometimes, the cleanest logic is no logic at all—it's just data. Instead of writing complex switch-case or chained if-else statements, you can often use a lookup table.
For example, imagine a function that takes an integer input n (1 to 12) and returns the number of days in that month. A novice approach might use a massive switch statement. A table-driven approach is $O(1)$ and readable:
int
To make this production-ready, we need to handle leap years and validate input. Notice how we can handle the logic without creating deep branches:
int
This concept extends beyond arrays. You can use std::map or std::unordered_map to map inputs to values, or even inputs to functions (function pointers or lambdas), effectively replacing complex control structures with hash lookups.
Early Return (Guard Clauses)
One of the easiest ways to reduce nesting is the Early Return pattern, also known as Guard Clauses. Instead of wrapping your "happy path" logic inside an if block, you invert the condition and return (or throw) immediately.
The Nested Way
Here, the core logic is buried inside error checking:
void
The Flattened Way
By handling the edge cases first, the "happy path" remains at the root indentation level:
void
This aligns with the mental model of most developers: handle the preconditions, then do the work.
State Design Pattern
Finite State Machines (FSM) are often implemented with massive switch-case blocks that check a state variable. As the complexity grows, this becomes unreadable.
The State Pattern leverages polymorphism to flatten this logic. Instead of if (state == ON), the code delegates behavior to an object representing the current state.
Imagine a ceiling fan where the buttons do different things depending on the current speed.
The Implementation
We define an interface FanState and concrete states.
; // Forward declaration
;
;
// Concrete State: Stopped
;
In the usage code, there are no conditionals. The logic is encapsulated within the classes:
int
This removes deeply nested logic regarding state transitions from your main controller class. You can read more about this pattern here.
Factory Method
Similar to the State pattern, the Factory Method allows you to encapsulate the creation logic. If you find yourself writing if (type == "A") return new A(); else if (type == "B")..., a Factory pattern can hide that branching complexity, keeping your main business logic flat.
Use Higher-Order Functions
Modern C++ (C++11 and beyond) provides powerful tools in the
The Loop Way
loops often lead to nesting, especially when combined with filtering (if inside for).
std::vector<int> input = ;
std::vector<int> output;
for
The Algorithm Way
Using standard algorithms flattens the logic and makes the intent (Filter -> Map) clear.
std::vector<int> input = ;
std::vector<int> output;
// Copy only if even
;
// Transform (Map) in place to square them
;
With C++20 Ranges, this becomes even more declarative and completely flat, resembling the functional style of Rust or Python.
Conclusion
Nesting isn't inherently bad, but deep nesting hides bugs and hurts readability. By using Table-Driven methods for static data, Guard Clauses for error checking, and Design Patterns for complex state behavior, you can keep your code as flat as the logic allows.