0

Nested control flow and cyclomatic complexity

Intermediate5 min read·php-05-008
interviewsolid

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:

  1. Guard clauses / early return: Handle error cases first and return, leaving the happy path at the lowest indentation level.
  2. Extract method: Move a complex conditional block into a well-named function.
  3. Replace conditional with polymorphism: Replace if (type === 'x') dispatch with method calls on different classes.
  4. Strategy pattern: Extract conditional branches into interchangeable objects.

Code Example

php
<?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; }
}