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 complexity | Nested / cognitive complexity | |
|---|---|---|
| Question it answers | How many paths must be tested? | How hard is this to read and change? |
| Origin | Thomas McCabe, 1976 | Cognitive Complexity by G. Ann Campbell, Sonar |
| Nesting | Ignored | Each level adds to the penalty |
Flat switch | Grows with every case | Counts once |
| Best for | Test planning, branch coverage | Review, 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.
| Function | Cyclomatic | Cognitive |
|---|---|---|
switch with 12 flat cases, one return each | 13 | 1 |
Four nested if blocks | 5 | 10 |
| The same four checks as guard clauses | 5 | 4 |
Scroll the table sideways to see every column.
The flat switch looks scary to a path counter and is trivial to read:
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:
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:
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 shape | Cyclomatic | Nesting cost | Typical action |
|---|---|---|---|
| Flat validation rules | Medium | Low | Leave it if tests are clear |
Long switch | High | Low | Table-driven dispatch if it keeps growing |
| Nested business rules | Medium | High | Guard clauses or helper functions |
| Parser or state machine | High | Medium | Split by state or phase |
| Permission matrix | High | High | Rule 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.
- Guard clauses. Return early on a failed precondition so the happy path sits at the left margin.
- Extract by phase. A function that parses, validates and executes nests naturally; split it into three.
- Replace ladders with data. A lookup table beats a stable
if/elifchain. - 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.
repowise health # fix-first list, KPIs, lowest-scoring files
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 orcatchinside 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
ifblocks 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.



