Nested Complexity vs Cyclomatic Complexity: What Each Measures

Raghav ChamadiyaUpdated 7 min read
On this page

Cyclomatic complexity counts the independent paths through a function, which tells you how many tests it needs. Nested complexity, and its standardized form cognitive complexity, charges extra for every level of nesting, which tells you how hard the function is to read. The two disagree on the same code, and the disagreement is the useful part: a flat switch is cheap to read and expensive to test, while a four-level if ladder is the reverse.

Part of the code health guide.

Nested complexity vs cyclomatic complexity at a glance

Cyclomatic complexityNested / cognitive complexity
Question it answersHow many paths must be tested?How hard is this to read and change?
OriginThomas McCabe, 1976Cognitive Complexity by G. Ann Campbell, Sonar
NestingIgnoredEach level adds to the penalty
Flat switchGrows with every caseCounts once
Best forTest planning, branch coverageReview, refactoring targets

Scroll the table sideways to see every column.

"Nested complexity" is not a single published standard. Teams use it for maximum nesting depth, or for the nesting part of cognitive complexity. This guide uses the second meaning and shows scores from the standardized metric.

What cyclomatic complexity measures

Cyclomatic complexity is the number of linearly independent paths through a function's control-flow graph. In practice you start at 1 and add 1 for each if, loop, case, catch, and each && or || (definition on Wikipedia). The result is the minimum number of test cases for full branch coverage.

McCabe suggested 10 as a practical ceiling per function, and most linters default near it. The number says nothing about layout: ten statements in a row and ten statements nested ten deep score the same.

What nested (cognitive) complexity measures

Cognitive complexity scores how much a reader has to hold in their head. Sonar's published rules give it three ideas: ignore shorthand that makes code easier to read, add 1 for each break in linear flow (if, loops, catch, a whole switch), and add an extra increment for each level of nesting that a flow-breaking structure sits inside.

That nesting increment is the "nested complexity" part. An if at the top level costs 1. The same if inside three other structures costs 4. A switch counts once however many cases it has, because a reader scans the cases rather than tracing them.

Same code, two scores

The same functions get opposite rankings from the two metrics. Scores below follow the cognitive complexity rules above and the cyclomatic definition.

FunctionCyclomaticCognitive
switch with 12 flat cases, one return each131
Four nested if blocks510
The same four checks as guard clauses54

Scroll the table sideways to see every column.

The flat switch looks scary to a path counter and is trivial to read:

ts
switch (status) {
  case "queued": return "waiting";
  case "running": return "active";
  case "failed": return "error";
  // nine more flat cases
}

The nested ladder looks modest to a path counter and is the one that causes mistakes:

py
def enable(user, feature_flag_enabled):
    if user:                              # +1
        if user.is_active:                # +2 (nesting 1)
            if user.plan == "pro":        # +3 (nesting 2)
                if feature_flag_enabled:  # +4 (nesting 3)
                    enable_feature()      # cognitive 10, cyclomatic 5

Guard clauses keep every path and flatten the shape:

py
def enable(user, feature_flag_enabled):
    if not user:                  # +1
        return
    if not user.is_active:        # +1
        return
    if user.plan != "pro":        # +1
        return
    if not feature_flag_enabled:  # +1
        return
    enable_feature()              # cognitive 4, cyclomatic 5

Cyclomatic complexity cannot see that refactor. Cognitive complexity drops from 10 to 4, and so does the real cost of adding a fifth condition.

Which metric to use for what

Use cyclomatic complexity to size tests and cognitive complexity to choose refactors. Cyclomatic complexity answers how many cases to write and whether a change added branches. Cognitive complexity answers whether a reviewer can follow the function and whether the next edit is safe. A function flagged by both, high paths and deep nesting, is the strongest refactoring candidate.

Code shapeCyclomaticNesting costTypical action
Flat validation rulesMediumLowLeave it if tests are clear
Long switchHighLowTable-driven dispatch if it keeps growing
Nested business rulesMediumHighGuard clauses or helper functions
Parser or state machineHighMediumSplit by state or phase
Permission matrixHighHighRule table or policy objects

Scroll the table sideways to see every column.

Thresholds that work in review

Start from McCabe's 10 for cyclomatic complexity and Sonar's default of 15 for cognitive complexity, then adjust per codebase. For nesting depth, three levels deserves a look and four usually needs flattening. Treat all of these as review triggers, not laws, and tighten them on the code that changes most.

How to flatten nested code

Flatten the shape without changing behavior, and the score follows.

  1. Guard clauses. Return early on a failed precondition so the happy path sits at the left margin.
  2. Extract by phase. A function that parses, validates and executes nests naturally; split it into three.
  3. Replace ladders with data. A lookup table beats a stable if/elif chain.
  4. Keep inner blocks tiny. If you must nest, make the inner block a single named call.

In a pull request, ask whether the diff added an indentation level, hid the success path, or moved a branch deeper without removing one.

How repowise scores both

repowise reports cyclomatic complexity and deep nesting as separate findings and ranks files by them together with git history. Every file gets a 1 to 10 health score from deterministic detectors. The structural family covers deep nesting, brain methods, god classes and complex conditions, and the size family covers long or high-CCN functions (code health docs). No LLM key is needed.

bash
repowise health            # fix-first list, KPIs, lowest-scoring files
text
Fix first
 1. Extract the retry branch from parse_config  now, effort M, +1.4 health
    src/config/loader.py:212
    CCN 47 and nesting 6 in a file changed 19 times this quarter.

The last line is the point: complexity and nesting are read next to churn. A deeply nested function nobody touches can wait. The same function in a file changed 19 times this quarter goes to the top. See the same ranking on a public repository in the FastAPI code health demo, read the code health feature page, or continue with how to find dead code.

FAQ

What is nested complexity in code?
Nested complexity is the reading cost created by stacked control flow. Each if, loop or catch inside another adds more than the same structure at the top level, because the reader must keep every enclosing condition in mind. Cognitive complexity is the standardized metric that includes this penalty.
Is nested complexity the same as cyclomatic complexity?
No. Cyclomatic complexity counts independent paths and ignores layout. Nested complexity charges for depth. Four nested if blocks score 5 on cyclomatic complexity and 10 on cognitive complexity.
Is cognitive complexity better than cyclomatic complexity?
For readability and refactoring decisions, yes, because it was designed to match how developers judge understandability. For counting tests, cyclomatic complexity is still the right number. Most teams track both.
What is a good threshold for nested complexity?
Flag functions that nest three or more levels, and treat four as a refactor trigger. For cognitive complexity, Sonar's default threshold is 15 per function. These are heuristics, so lower them for code that changes often.
Why do complexity metrics disagree?
They measure different things: paths, depth, boolean operators or maintainability. A flat switch maximizes path count while a nested ladder maximizes depth, so each metric ranks them differently on purpose.
Does lowering cyclomatic complexity make code easier to read?
Not always. Splitting a function lowers each part's path count, but if the nesting stays, the reading cost is unchanged. Guard clauses and phase extraction target the nesting directly.