Cyclomatic complexity, explained
Cyclomatic complexity is a count of the independent paths through a function's logic. It was introduced by Thomas McCabe in 1976 as a way to put a number on something programmers already sensed intuitively: some functions are simple to reason about, and some are a tangle.
What actually gets counted
Every function starts at a baseline of 1 — one straight-line path with no branching. From there, the count goes up by one for every point where execution can fork: an if, a loop (for, while), a case in a switch, a catch block, a ternary ?:, and short-circuit boolean operators like && and ||. Each of these represents a decision point — a place where the function can take one of at least two routes.
The result is a rough measure of how many distinct paths a test suite would need to exercise every branch at least once. A function with a complexity of 1 has exactly one path. A function with a complexity of 12 has at least 12 meaningfully different routes through it.
Why it correlates with bugs
Complexity isn't inherently bad — some problems are genuinely branchy. But high complexity in practice tends to mean a few things at once: the function is doing more than one job, it's harder for a reader to hold the whole thing in their head, and it's harder to test thoroughly because the number of paths to cover grows fast. Research going back to McCabe's original work has repeatedly found a correlation between high cyclomatic complexity and higher defect rates — not because the number itself causes bugs, but because it's a proxy for exactly the kind of tangled logic that's easy to get wrong and hard to review.
What counts as "too high"
There's no universal cutoff, but common conventions treat single-digit complexity as comfortable, 10–20 as worth a second look, and anything higher as a strong candidate for refactoring. The number matters less in isolation than relative to the rest of your codebase — a function that's a clear outlier compared to its neighbors is usually the one worth investigating first, regardless of the exact threshold you use.
Bringing it down
- Extract sub-functions. Pulling a branchy block out into its own well-named function doesn't remove the complexity from the codebase, but it isolates it and makes the caller's path count drop.
- Replace nested conditionals with early returns. Guard clauses that exit early often reduce nesting depth even when the branch count stays the same, which makes the remaining logic easier to follow.
- Replace long if/else or switch chains with a lookup. A dictionary or map from condition to behavior can replace many branches with one lookup plus a call.
- Split functions that do two things. High complexity often means a function is validating, transforming, and reporting all at once — separating those responsibilities usually collapses the path count in each piece.
How REPO-SIGHT reports it
REPO-SIGHT's parser walks the token stream of each C++, Python, or Java file and increments a per-function counter at every branch point — loops, conditionals, case labels, catch blocks, ternaries, and short-circuit operators — the same decision points described above. Per-function totals roll up into a project-wide cyclomatic complexity figure, and complexity density (complexity relative to code volume) feeds into the overall health score at a 35% weight, the single largest factor in the grade. Run it on a repo to see your own numbers.