Nested control flow and cyclomatic complexity
Concept
Cyclomatic complexity is a metric that counts the number of linearly independent paths through a piece of code. Each if, elseif, else, for, foreach, while, do-while, catch, case, and &&/|| adds 1 to the complexity. The base is 1 (a function with no branches has complexity 1).
A function with cyclomatic complexity > 10 is considered complex; > 20 is considered very complex and should almost always be refactored. High complexity correlates strongly with: more bugs, harder testing (you need a test for each path), harder code review, and harder maintenance.
Why nested conditionals are especially bad: Each level of nesting adds visual indentation but more importantly adds multiplicative path complexity. Three nested if statements create up to 8 execution paths (2³). Four nested = 16 paths. A function with 4 levels of nesting and several branches in each is nearly impossible to fully test.
Reducing complexity:
- Guard clauses / early return: Handle error cases first and return, leaving the happy path at the lowest indentation level.
- Extract method: Move a complex conditional block into a well-named function.
- Replace conditional with polymorphism: Replace
if (type === 'x')dispatch with method calls on different classes. - Strategy pattern: Extract conditional branches into interchangeable objects.
Code Example
<?php
declare(strict_types=1);
// HIGH complexity — 8+ paths, 4 levels of nesting (BAD)
function processOrder_bad(array $order): string
{
if ($order['status'] === 'active') {
if ($order['payment']['method'] === 'credit_card') {
if ($order['payment']['verified']) {
if ($order['inventory']['available']) {
return 'Processing';
} else {
return 'Out of stock';
}
} else {
return 'Payment unverified';
}
} else {
return 'Unsupported payment';
}
} else {
return 'Order inactive';
}
}
// LOWER complexity — same logic, guard clauses (GOOD)
function processOrder_good(array $order): string
{
if ($order['status'] !== 'active') {
return 'Order inactive';
}
if ($order['payment']['method'] !== 'credit_card') {
return 'Unsupported payment';
}
if (!$order['payment']['verified']) {
return 'Payment unverified';
}
if (!$order['inventory']['available']) {
return 'Out of stock';
}
return 'Processing'; // happy path — no nesting
}
// Replacing conditional dispatch with polymorphism
// BAD: type-based dispatch with nested ifs
function calculateShipping_bad(string $type, float $weight): float
{
if ($type === 'express') {
return $weight * 5.0 + 10.0;
} elseif ($type === 'standard') {
return $weight * 2.0 + 3.0;
} elseif ($type === 'economy') {
return $weight * 1.5;
}
return 0;
}
// GOOD: polymorphism
interface ShippingCalculator
{
public function calculate(float $weight): float;
}
class ExpressShipping implements ShippingCalculator
{
public function calculate(float $weight): float { return $weight * 5.0 + 10.0; }
}