Cyclomatic Complexity: How to Measure Code Complexity
Learn how to calculate cyclomatic complexity from a control-flow graph, interpret the score, and use it to plan tests without treating it as a measure of code quality.
Cyclomatic complexity measures the decision structure of a software module by counting its linearly independent paths through a control-flow graph. For a single connected function graph, calculate V(G) = E − N + 2, where E is the number of directed edges and N is the number of nodes. For a graph with P connected components, use V(G) = E − N + 2P. In a conventional single-entry, single-exit function graph, an equivalent shortcut is to count decision points and add one.
The result is useful for identifying functions whose branching deserves attention and for planning basis-path tests. It is a structural metric: it does not tell you whether the code is correct, readable, secure, or easy to maintain. Always state what unit and counting convention produced a score.
1. What cyclomatic complexity measures
Let G be a control-flow graph for one function or other defined software module. Its nodes represent statements or expressions, and directed edges represent possible transfers of control. The graph’s cyclomatic complexity, commonly written V(G), v(G), or CC, is the number of linearly independent paths in that graph.
The graph-based definition and its use in structured testing are described in Arthur H. Watson and Thomas J. McCabe’s NIST SP 500-235, Structured Testing. A path is independent when it includes at least one edge not included in the paths already selected for the basis set. A basis set spans the graph’s control-flow paths; it is not a list of every possible runtime path through loops or data-dependent behavior.
2. How to calculate the score
- Choose the unit. Usually this is one function, method, or subroutine. Avoid reporting a repository-wide aggregate as though it described every function.
- Define the graph convention. Identify what counts as a node, edge, decision, and exit. Note whether exceptional control flow, short-circuit expressions, and language-specific constructs are included.
- Count nodes and edges. Draw the control-flow graph, or obtain it from a tool whose convention you can document.
- Count connected components. Use
Pfor the number of graph components. - Apply the formula. Calculate
E − N + 2P. For the usual single connected function graph,P = 1, so the formula becomesE − N + 2. - Report the unit and method with the number. A result is more useful when another developer can reproduce its scope and convention.
Example: one decision
Consider a function with an entry node, a decision, two alternative actions, and a shared exit. Its graph has five nodes and five edges, so V(G) = 5 − 5 + 2 = 2. The same result comes from one decision point plus one. A basis set needs two independent paths: one taking each outcome of the decision.
Example: two sequential decisions
For two binary decisions encountered in sequence, a conventional graph has three decision outcomes that introduce independent branches beyond the straight-through structure, giving a score of three. The possible executions may include four combinations of outcomes, but cyclomatic complexity is not a count of every combination. It is the size of a basis set under the graph model.
Decision-count shortcut and its limits
For a standard, single-entry, single-exit graph, count predicate or decision nodes and add one. For example, a function with three binary decision points has a conventional score of four. This shortcut is convenient, but can disagree with a graph-based count when the control-flow model or language features differ. Document how you treated compound Boolean expressions, exception handlers, switch or match branches, loops, and early returns.
3. A runnable Python calculation
This small program calculates the graph formula from an explicit edge list. Nodes can be any hashable values. It counts connected components in the underlying graph, then applies E − N + 2P. The edges are directed control transfers; direction does not change the connected-component count used here.
from collections import defaultdict
def cyclomatic_complexity(nodes, edges):
"""Calculate E - N + 2P for an explicit control-flow graph."""
nodes = set(nodes)
edges = list(edges)
if not nodes:
raise ValueError("Provide at least one node")
adjacency = defaultdict(set)
for source, target in edges:
if source not in nodes or target not in nodes:
raise ValueError(f"Edge references an unknown node: {(source, target)}")
adjacency[source].add(target)
adjacency[target].add(source)
components = 0
unseen = set(nodes)
while unseen:
components += 1
start = unseen.pop()
stack = [start]
while stack:
node = stack.pop()
neighbors = adjacency[node] & unseen
unseen.difference_update(neighbors)
stack.extend(neighbors)
return len(edges) - len(nodes) + 2 * components
# Entry -> decision; the two outcomes join before exit.
nodes = {"entry", "decision", "then", "else", "exit"}
edges = [
("entry", "decision"),
("decision", "then"),
("decision", "else"),
("then", "exit"),
("else", "exit"),
]
print(cyclomatic_complexity(nodes, edges)) # 2
This is a calculator for a graph you supply, not a source-code parser. A source analyzer must build a control-flow graph according to the target language’s semantics before this formula can be applied. Parallel edges are counted separately, as they represent separate control transfers in the provided graph.
4. Interpreting the result and using it for tests
A larger score means the modeled module contains more independent control-flow paths. It can help a team find functions to inspect, estimate the breadth of basis-path testing, and track changes in a particular function over time. It does not prescribe a universal acceptable threshold. The research dossier’s primary sources do not establish a current cross-industry cutoff, so treat any threshold as local policy.
NIST SP 500-235 describes structured testing in which decision outcomes are exercised independently. Its executive summary states: “The number of tests required for a software module is equal to the cyclomatic complexity of that module.” That is a statement about the report’s structured-testing method, not a universal rule that every modern test suite must contain exactly that many tests. A test can exercise multiple paths, and loops or input domains can introduce concerns beyond a basis set.
Use the metric as one input to test planning:
- Identify a basis set of independent paths for a function.
- Check that tests exercise the relevant decision outcomes.
- Review boundary values, error handling, and important data combinations separately.
- Keep assertions focused on behavior, not merely on traversing a line.
- Use code review and other quality evidence alongside the score.
Cyclomatic complexity alone does not measure correctness, readability, security, data complexity, or maintainability. NIST’s research on static analysis reports that code complexity can make weaknesses harder for analysis tools to detect; that is a broader observation about analysis challenges, not evidence that a cyclomatic score alone predicts defects. See NIST IR 8165, Impact of Code Complexity on Software Analysis.
5. Comparing measurements across tools
Two tools can report different values for the same source because they may build or summarize control-flow graphs differently. Before comparing numbers, check:
| Question | Why it matters |
|---|---|
| What is the unit? | A per-function result is not directly comparable to a class or repository aggregate. |
| Which constructs count as decisions? | Compound Boolean expressions, switch or match arms, and exception paths can be modeled differently. |
| How are exits and disconnected code handled? | Graph boundaries and unreachable or exceptional paths affect node and edge counts. |
| Is the output a maximum, sum, or distribution? | An aggregate can hide a single high-complexity function or make a codebase with many small functions look large. |
| Can the tool’s convention be reproduced? | Documenting the tool and configuration makes later comparisons more meaningful. |
Do not treat a cross-tool score difference as a code change until you have checked the analyzed unit and graph conventions. The sources cited here establish the graph basis of the metric; they do not establish a single current cross-tool conformance standard.
6. Practical measurement workflow
- Choose a specific function or module that needs review.
- Run a complexity analyzer configured for the language and repository.
- Inspect the function and, when the result matters, the tool’s graph or documented counting rules.
- Record the tool, version or configuration, unit, and score together.
- Use the result to select paths and decision outcomes for tests.
- Refactor only when it improves the code’s actual structure or makes behavior easier to understand and verify.
- Recalculate using the same tool and scope if you want a meaningful before-and-after comparison.
7. Common errors and fixes
| Problem | Likely cause | Fix |
|---|---|---|
Using E − N + 2 on a graph with multiple components |
The single-component shortcut was applied outside its assumption. | Count components and use E − N + 2P. |
Adding one for every if and assuming the total is definitive |
The shortcut may not match the tool’s treatment of compound conditions or language constructs. | Use the tool’s documented convention or calculate from its graph; state the convention. |
| Calling the score a count of all execution paths | Independent basis paths were confused with every possible runtime path. | Describe it as the number of linearly independent paths in the modeled graph. |
| Applying a threshold as a universal quality rule | A local policy was mistaken for a general standard. | Label thresholds as team policy and consider function context, tests, and review evidence. |
| Comparing a repository total with a function score | The measurement units differ. | Compare the same unit and aggregation method. |
| Assuming a low score proves safe or maintainable code | The metric was treated as a general quality measure. | Review behavior, data handling, readability, security, and tests separately. |
| Getting a negative or unexpected result from a manual graph | Edges or nodes may be missing, or the graph boundaries and components may be inconsistent. | Verify every transfer of control, include entry and exit consistently, and recount E, N, and P. |
8. Performance, reliability, and cost
The arithmetic is linear in the graph size: counting nodes and edges and finding connected components takes O(N + E) time and O(N + E) space for an adjacency representation. In ordinary source-analysis workflows, parsing and graph construction are generally the work that surrounds this calculation; no benchmark is implied here.
For reliable tracking, pin the analyzer version or configuration where practical, measure the same unit, and preserve the result with its scope. A score can shift after a tool changes its treatment of syntax even when the source remains identical. Cyclomatic complexity itself has no per-calculation service cost; analyzer licensing or compute costs depend on the particular tool and environment, which are outside the sources available for this article.
9. Or skip the browser setup
If you are collecting screenshots of documentation, test output, or code examples while reviewing a project, ScreenshotNeo is a website screenshot API and MCP server. A single GET request returns a PNG, JPEG, WebP, or PDF. It is separate from cyclomatic-complexity measurement; this option is for the browser capture step.
For setup and options, see the ScreenshotNeo API documentation.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture, and each of those steps can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account for 1,000 screenshots a month, with no card.
10. FAQ
Is cyclomatic complexity the same as the number of tests I need?
Not universally. It gives the size of a basis set for the modeled graph and supports a structured-testing approach. Test counts depend on the test design and the behaviors and input conditions that matter.
Can a function have a score of one?
Yes. A straight-line, single connected function with no decisions has a conventional cyclomatic complexity of one.
Does reducing the score always improve the code?
No. A lower score does not by itself establish better behavior or readability. Assess the refactoring in context and keep the tests and review evidence.
Should I report a single complexity number for an entire project?
Prefer per-function results or a clearly named distribution or aggregate. A lone total obscures which modules contribute to it and cannot describe their individual structure.
References
- Arthur H. Watson and Thomas J. McCabe, NIST SP 500-235: Structured Testing: A Testing Methodology Using the Cyclomatic Complexity Metric (1996).
- Charles D. De Oliveira, Elizabeth Fong, and Paul E. Black, NIST IR 8165: Impact of Code Complexity on Software Analysis (2017).


