Cyclomatic Complexity: What It Is and How to Fix It

Summary

Cyclomatic complexity counts the independent execution paths through a function. The formula is CC = decision points + 1. Scores below 10 are fine; above 15 means refactoring is overdue. Guard clauses, extracted helper functions, and lookup tables are the fastest ways to bring the number down. Cognitive complexity measures the same problem from the readability angle and is worth tracking alongside it.

Cyclomatic complexity is a number that tells you how many independent execution paths exist through a piece of code. A function with one if statement has two paths: one where the condition is true, one where it is false. Every new if, while, for, or case adds another path to that count.

Data analysts, ops teams reviewing sprint metrics, and anyone who reads static analysis reports will encounter this number regularly. It shows up in pull request checks, code quality dashboards, and automated linting pipelines.

The metric was invented by Thomas McCabe in 1976 and remains the standard benchmark when engineers ask: "Is this function getting too hard to test?" The short version: keep it below 10. Above 15, you are in refactoring territory. Biscuit would have fetched a simpler function from the start.

The Formula Behind Cyclomatic Complexity

The full formula looks like this:

M = E - N + 2P

Where:

For most cases, you do not need to draw the control flow graph. The shortcut is faster:

CC = Decision Points + 1

Count every if, else if, while, for, &&, ||, case, or ternary operator in the function. Add 1. That is your cyclomatic complexity.

A function with zero branches scores 1 -- it is a straight line from start to finish. Add one if, and you are at 2. Add five more conditions and you are at 7, which is still manageable. Add ten more and now you have a problem.

The score directly maps to the minimum number of test cases required to cover every path. A function with CC = 10 needs at least 10 tests for full path coverage. That alone makes the number useful during sprint planning.

Calculating It by Hand: A Real Example

Take this Python function:

def process_order(order, user):
    if not order:
        return None
    if not user.is_active:
        return None
    if order.total > user.credit_limit:
        if not user.has_override:
            return None
    if order.requires_signature and not user.has_signed:
        return None
    return order.confirm()

Walk through the decision points:

Total: 6 decision points + 1 = CC of 7.

Manageable right now, but accumulating fast. Add two more input-validation conditions and you are at 9. Add one sprint of new business rules and you hit 13 -- that is when code reviewers start drawing red circles.

The nested condition (if not user.has_override) is the quiet danger. Sequential conditions are easier to read; nested conditions multiply the paths exponentially.

Whiteboard control flow graph showing decision nodes and branches for cyclomatic complexity

What Your Score Actually Means

Thresholds differ slightly by organization and domain, but the standard ranges are:

For safety-critical systems (medical devices, aviation software, financial engines), many teams set the hard limit at 5 to 7. The NIST guidelines recommend no higher than 10 for functions in regulated software. Regular product code generally lives comfortably under 10.

One caveat worth noting: the score counts decisions, not difficulty. A switch statement with 20 simple string mappings scores 21 but takes five seconds to read. A 3-level nested block might score 4 and take five minutes to understand. The number is a signal, not a verdict.

Cyclomatic vs. Cognitive Complexity: The Real Difference

Cyclomatic complexity was designed to tell you how many test cases you need. It treats all decision points equally: one if counts the same whether it is nested three levels deep or sitting at the top of the function.

Cognitive complexity, introduced by SonarSource, measures something different: how hard the code actually is to read. It penalizes nesting more aggressively than sequential conditions, which better reflects how developers actually process code.

The practical difference:

# Version A: 3 sequential conditions -- CC = 4, Cognitive = 3
def check_a(x, y, z):
    if x > 0:
        return False
    if y > 0:
        return False
    if z > 0:
        return False
    return True
# Version B: 3 nested conditions -- CC = 4, Cognitive = 6
def check_b(x, y, z):
    if x > 0:
        if y > 0:
            if z > 0:
                return False
    return True

Both score CC = 4. But Version B is harder to read -- you need to hold the outer condition in your head while parsing the inner ones. Cognitive complexity correctly flags Version B as more demanding.

Use cyclomatic complexity for test planning and defect risk assessment. Use cognitive complexity to spot code that will slow your team down during review. They answer different questions.

Two software engineers doing a code review at a standing desk

Complex Excel Formulas Have the Same Problem

This is where it gets relevant for spreadsheet workers.

A deeply nested IF formula has exactly the same structure as deeply nested code. Each condition adds a path, and each layer of nesting adds cognitive load. The formula below has four decision points, giving a CC equivalent of 5:

=IF(A2="Sales",IF(B2>10000,"Tier 1",IF(B2>5000,"Tier 2","Tier 3")),IF(A2="Support","Fixed","Other"))

That is not terrible yet. But add one more tier or one more department and the formula becomes the kind of thing nobody wants to edit six months later -- including the person who wrote it.

The fix in Excel mirrors the fix in code: flatten the nesting. Use IFS() when you have multiple conditions returning different values from the same column:

=IFS(AND(A2="Sales",B2>10000),"Tier 1",AND(A2="Sales",B2>5000),"Tier 2",A2="Sales","Tier 3",A2="Support","Fixed",TRUE,"Other")

Or better: extract the tier logic to a helper column and reference it in your main formula. Two simple formulas are easier to audit than one complex one. Voila ce que ca donne dans une vraie cellule: the complexity drops, the logic stays.

Five Ways to Lower Your Cyclomatic Complexity

1. Extract helper functions.

If a function validates input, transforms data, and writes to a database, it is doing three jobs. Split it into three functions. Each drops to a lower score and can be tested independently.

2. Use early returns (guard clauses).

Instead of nesting success logic inside multiple if blocks, reject the bad cases early and return. The happy path stays at the left margin.

# Before: nested -- harder to follow
def process(user):
    if user:
        if user.is_active:
            if user.has_permission:
                return do_work(user)
    return None

# After: guard clauses -- easier to extend
def process(user):
    if not user:
        return None
    if not user.is_active:
        return None
    if not user.has_permission:
        return None
    return do_work(user)

Same cyclomatic complexity in both cases, but the guard-clause version has lower cognitive complexity and is easier to extend.

3. Simplify boolean expressions.

A long if (a and b and not c and d) condition adds multiple decision points and is hard to read. Extract it to a named variable: eligible = a and b and not c and d. The if eligible: line reads in plain English and the intent becomes clear.

4. Replace conditionals with lookup tables.

A long if/elif chain that maps values to outputs is a hidden lookup table. Replace it with a dictionary:

# Before: CC = 5 for 4 branches
def get_rate(category):
    if category == "A":
        return 0.05
    elif category == "B":
        return 0.10
    elif category == "C":
        return 0.15
    else:
        return 0.20

# After: CC = 1
RATES = {"A": 0.05, "B": 0.10, "C": 0.15}
def get_rate(category):
    return RATES.get(category, 0.20)

5. Split large functions by concern.

If a function runs past 25 lines, ask what it actually does. Functions handling multiple concerns accumulate decision points from all of them. Breaking them apart makes each piece easier to test and reuse.

Tools That Calculate It Automatically

You do not have to count by hand on every pull request. Several tools integrate directly into standard development workflows:

A practical setup: enforce a warning at 10 and a hard error at 15. That lets you catch drift without blocking every pull request on a minor overage. Review anything above 10 in code review; automate the block at 15.

Frequently asked questions

What is cyclomatic complexity?
Cyclomatic complexity is a software metric that counts the number of independent execution paths through a function or module. It was introduced by Thomas McCabe in 1976 and is calculated using the formula CC = decision points + 1, where decision points include if statements, loops, case clauses, and logical operators like && and ||.
What is a good cyclomatic complexity score?
Scores between 1 and 10 are generally considered acceptable for most production code. Scores from 11 to 15 suggest the function is getting complex enough to warrant peer review and possible refactoring. Anything above 15 is a strong candidate for splitting, and above 20 is associated with significantly higher defect rates. Safety-critical systems often target a maximum of 5 to 7.
How do you calculate cyclomatic complexity manually?
The fastest method: count every if, else if, while, for, case, ternary operator, and logical operator (&& or ||) in the function. Add 1. That is your cyclomatic complexity score. The full formula is M = E - N + 2P (edges minus nodes plus 2 times connected components), which is equivalent but requires drawing the control flow graph.
What is the difference between cyclomatic complexity and cognitive complexity?
Cyclomatic complexity counts execution paths and is useful for estimating test coverage requirements. Cognitive complexity measures how hard the code is to read by penalizing nesting more heavily than sequential conditions. Two functions can have identical cyclomatic complexity scores but very different cognitive complexity scores if one uses deep nesting and the other uses guard clauses.
What causes high cyclomatic complexity?
The most common causes are functions that handle too many responsibilities, deeply nested conditional logic, long if/elif/switch chains that map inputs to outputs, and functions that have grown organically over multiple sprints without refactoring. Business logic with many edge cases is a natural contributor, but even legitimate domain complexity can often be restructured using lookup tables or extracted helper functions.
Does cyclomatic complexity apply to Excel formulas?
Not formally, but the underlying concept applies directly. A nested IF formula has the same structural problem as nested code: each condition adds a decision path, and each level of nesting increases cognitive load. Replacing deeply nested IF chains with IFS(), using helper columns, or breaking a complex formula into two simpler ones follows the same logic as refactoring high-complexity code.
Which tools measure cyclomatic complexity automatically?
SonarQube and SonarCloud measure it per function on every pull request. Radon is the standard tool for Python (radon cc file.py -s). ESLint has a built-in complexity rule for JavaScript. CodeClimate tracks trends over time across a codebase. For VS Code users, the CodeMetrics extension shows scores inline next to function definitions.