Understanding Cyclomatic Complexity -- NDepend (original) (raw)

May 25, 2026 9 minutes read

Cyclomatic Complexity in C#

Cyclomatic Complexity (or CC) in C# is a code metric that counts the number of linearly independent execution paths through a method. Concretely, it is computed as 1 plus the number of branching constructs in the method body (such as if, while, for, case, &&, ||, ?: and ??). The higher the score, the harder the method is to read, test and safely change. A score of 1 means a single straight path, around 10 is the traditional upper bound recommended by Thomas McCabe, and anything above 25 is flagged as excessive by Microsoft’s CA1502 analyzer.

This guide explains, with C# examples, how Cyclomatic Complexity is calculated, what thresholds matter in practice, how to measure and visualize it in real .NET codebases, and how to go beyond the raw score by pairing it with test coverage and IL-level analysis.

Cyclomatic Complexity was introduced by Thomas J. McCabe in 1976 as a way to quantify the structural complexity of a piece of code. The idea comes from graph theory: every method can be represented as a control flow graph where nodes are blocks of statements and edges are jumps between them. On that graph, the Cyclomatic Complexity is given by the classic formula:

where E is the number of edges, N the number of nodes, and P the number of connected components. For a regular method with a single entry and a single exit, this collapses to 1 + the number of decision points, which is the form most tools actually compute.

What this number really tells you is the minimum number of test cases you need to exercise every independent path through the method. That is why Cyclomatic Complexity has stuck around for almost half a century: it is a structural metric, but it has a very concrete operational meaning for everyone who has to maintain or test the code.

Definition of Cyclomatic Complexity in C#

The Cyclomatic Complexity for a C# method is concretely 1 + {the number of following expressions found in the body of the method}:

| | if while for foreachcase default continuegoto && || catchternary operator ?: ??and or | | ------------------------------------------------------------------------------------------------- |

The following expressions are not counted for CC computation:

| | else do switch try usingthrow finally returnobject creationmethod callfield access | | ------------------------------------------------------------------------------------- |

Two details that trip people up: else does not increment the score because the alternative path was already created by its matching if; and a switch contributes one unit per case (and one for default), not one for the switch keyword itself. C# pattern-matching constructs (and, or, the modern switch expression with patterns) also add to the score, the same way their classic counterparts do.

Example of Cyclomatic Complexity Impact in C#

Exhibiting a Complex Method

Here is a complex method with entangled if and else scopes. The keyword if is used six times and && is used once. Hence its Cyclomatic Complexity score is 8:

1234567891011121314151617181920212223242526272829303132333435 public static class OrderLogic { public static void ProcessOrder( int orderId, bool isPriority, bool isInternational, bool isGift, bool isCouponApplied, decimal orderTotal) { if (orderId <= 0) { Console.WriteLine("Invalid order ID."); return; } if (isPriority) { Console.WriteLine("Processing priority order."); if (isInternational) { Console.WriteLine("Processing international priority order."); if (isGift) { Console.WriteLine("This is a gift order."); } } } else { Console.WriteLine("Processing standard order."); if (isInternational) { Console.WriteLine("Processing international standard order."); } } if (isCouponApplied && orderTotal > 100) { Console.WriteLine("Applying discount for orders over $100."); } else { Console.WriteLine("No discount applicable."); } }}

Eight independent paths means at least eight tests to fully cover this single method, plus a non-trivial amount of head-scratching every time someone has to add a new business rule. This is exactly the kind of method where a regression slips in unnoticed.

Refactoring the Complex Method in Several Simpler Methods

The method above can be refactored into several less complex methods. In the code we use CC to refer to each method Cyclomatic Complexity score:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 public static class OrderLogic { public static void ProcessOrder( int orderId, bool isPriority, bool isInternational, bool isGift, bool isCouponApplied, decimal orderTotal) { // CC 2 if (!IsValidOrder(orderId)) return; ProcessOrderType(isPriority, isInternational, isGift); ApplyDiscountIfEligible(isCouponApplied, orderTotal); } private static bool IsValidOrder(int orderId) { // CC 2 if (orderId <= 0) { Console.WriteLine("Invalid order ID."); return false; } return true; } private static void ProcessOrderType( bool isPriority, bool isInternational, bool isGift) { // CC 5 if (isPriority) { Console.WriteLine("Processing priority order."); if (isInternational) { Console.WriteLine("Processing international priority order."); } } else { Console.WriteLine("Processing standard order."); if (isInternational) { Console.WriteLine("Processing international standard order."); } } if (isGift) { Console.WriteLine("This is a gift order."); } } private static void ApplyDiscountIfEligible( // CC 3 bool isCouponApplied, decimal orderTotal) { if (isCouponApplied && orderTotal > 100) { Console.WriteLine("Applying discount for orders over $100."); } else { Console.WriteLine("No discount applicable."); } }}

Benefits of Refactoring

Note that the total Cyclomatic Complexity summed across the four methods is actually slightly higher than the original 8. That is fine and even expected. What matters for maintainability is the complexity per method, because that is the unit a developer has to reason about at a time.

Cyclomatic Complexity Thresholds: What Score Is Too High?

There is no single sacred number, but the literature converges around the same ranges. The table below summarises what most teams and tools use as a guideline:

Cyclomatic Complexity Risk profile Practical interpretation
1 – 10 Simple, low risk McCabe’s original recommendation. Easy to test, easy to read.
11 – 20 Moderately complex Still manageable, but worth a second pair of eyes during review.
21 – 50 Complex, high risk Hard to test exhaustively. Strong refactoring candidate.
> 50 Untestable Bug magnets. Often legacy hotspots that need to be broken down.

Two reference points are worth keeping in mind. McCabe himself recommended splitting modules that exceed a Cyclomatic Complexity of 10. Microsoft’s CA1502 analyzer defines “excessive complexity” as a score greater than 25 by default. Mark Seemann argues for an even tighter ceiling of around 7, mirroring Miller’s “magical number seven, plus or minus two” for human short-term memory.

In practice the right threshold depends on the codebase. A parser, a serializer or a state machine will routinely live in the 15-25 range without being objectively bad. A piece of business logic that scores 25 almost always is.

Measuring Cyclomatic Complexity in C#

You can’t improve what you don’t measure, so using a tool to evaluate code complexity is essential. Calculating this metric helps developers identify areas that might need refactoring to improve code quality.

NDepend is a great option for this, as it measures the cyclomatic complexity of methods in C# code. For instance, it includes the Search Methods by Complexity feature, which helps identify complex methods for further analysis.

Visual Studio itself ships a “Calculate Code Metrics” command (Analyze > Calculate Code Metrics) that reports Cyclomatic Complexity per method, type and assembly. The CA1502 analyzer can be wired into your build to actually fail on methods above a configured threshold, which is useful for new code. Roslyn-based analyzers like SonarAnalyzer.CSharp and third-party tools such as ReSharper or CodeRush also surface the same metric inside the editor.

Ruling C# Cyclomatic Complexity

NDepend offers several rules like Avoid methods too big, too complex that flag methods with excessively high Cyclomatic Complexity scores, highlighting potential issues in the code.

You are probably working with a large legacy codebase, making it impractical to refactor every complex method. This is why it’s essential to measure Cyclomatic Complexity against a baseline, allowing you to focus on new or refactored methods that are too complex. There are two rules for that:

This baseline-driven approach matters more than any absolute threshold. When you start tracking complexity on a well-established codebase, you will inevitably find complex methods that have been stable and well-tested for years. The real risk is not the static score, it is what happens when those methods start growing. Using NDepend’s CQLinq, you can express that idea directly:

| | // Cyclomatic Complexity got worsewarnif count > 0from m in JustMyCode.Methodswhere m.CodeWasChanged() && m.OlderVersion().CyclomaticComplexity < m.CyclomaticComplexity && m.OlderVersion().CyclomaticComplexity > 10select new { m, OldComplexity = m.OlderVersion().CyclomaticComplexity, m.CyclomaticComplexity } | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

The query warns whenever an already-complex method (CC > 10) becomes even more complex between two analysis snapshots. In other words, it surfaces the modifications that are actually risky, while leaving stable legacy untouched.

Visualizing C# Cyclomatic Complexity

A colored treemap can be used to visualize the cyclomatic complexity of your C# methods. In this visualization, each rectangle represents a method:

NDepend Visualize C# Cyclomatic Complexity

The advantage of a treemap over a flat list is that complexity hotspots literally jump out of the picture: a large, dark red rectangle inside an otherwise calm area is exactly the kind of method that deserves an architecture conversation.

C# Cyclomatic Complexity and Tests

Writing tests for your code is nowadays an essential practice for every professional C# developer. Typically, a test covers a single execution path, while the Cyclomatic Complexity score of a method represents the number of independent execution paths. Therefore, Cyclomatic Complexity provides a rough estimate of how many tests are required to fully test a method.

By running tests, you can determine the code coverage for each method. A method partially covered means that not all its independent execution paths are challenged by tests. The rule Methods should have a low C.R.A.P score spots methods that both have high Cyclomatic Complexity scores and are poorly tested (C.R.A.P stands for Change Risk Analyzer and Predictor). The matched methods clearly indicate pain points in your code and should be tested and refactored.

NDepend C# Cyclomatic Complexity and Code Coverage

The CRAP score defines a specific mathematical formula to combine complexity and coverage. Expressed in CQLinq, that formula is:

| | // CRAPfrom m in JustMyCode.Methodslet CC = m.CyclomaticComplexitylet uncov = (100 - m.PercentageCoverage) / 100flet CRAP = (CC * CC * uncov * uncov * uncov) + CCselect new { m, CRAP } | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

The CRAP score scales with the square of complexity and the cube of uncovered percentage, then adds the raw complexity as a floor. The practical takeaway: a method with CC = 30 and 100% coverage is far less of a liability than a method with CC = 12 and 0% coverage. The second data point of coverage shows that not all complexity is created equal.

Going Beyond Cyclomatic Complexity

Cyclomatic Complexity is a great start for reasoning about your code’s complexity, not the end of the conversation. Two extensions are worth knowing about.

Pair Complexity with Branch Coverage

If you have two methods with the same Cyclomatic Complexity, and one is fully covered by branch tests while the other has none, the risk profile is wildly different. A CQLinq query that captures this idea:

| | // Uncovered code in complex methodswarnif count > 0from m in JustMyCode.Methodswhere m.CyclomaticComplexity > 10 && m.PercentageBranchCoverage < 100select new { m, m.PercentageBranchCoverage, m.CyclomaticComplexity } | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

It raises a warning for any complex method without total branch coverage, which is a much more nuanced view of risk than “this method has CC > 10”.

IL Cyclomatic Complexity for Third-Party Code

Any non-trivial C# project drags in third-party libraries. Most of the time you treat them as black boxes; sometimes you regret it. NDepend’s CQLinq offers a property called IL Cyclomatic Complexity that applies the same metric to the .NET Intermediate Language inside the DLLs you depend on.

This lets you measure how complex the methods of a candidate library actually are, not just how nicely their public API is documented. If you analyze a dependency and find that its internal methods are routinely above CC = 30 in IL, that is a strong signal it will be hard to debug when something goes wrong inside it.

How to Reduce Cyclomatic Complexity in C#

Refactoring for lower Cyclomatic Complexity is mostly about extracting decisions out of the hot path. The techniques below come up over and over again on real codebases.

1. Extract Method

The most boring and the most effective. Pull a contiguous block of decisions into a well-named method, exactly like in the ProcessOrder example earlier. Each call site loses one chunk of complexity; each new method is small enough to test on its own.

2. Early Return / Guard Clauses

Instead of deeply nesting validation in if blocks, return early on invalid input. The cyclomatic count is the same, but the cognitive load drops sharply because every following line can assume the input is valid.

12345678910111213141516171819 // Nestedpublic decimal CalculateFee(Order order) { if (order != null) { if (order.IsValid) { if (order.Customer != null) { return order.Total * 0.05m; } } } return 0;}// Guardedpublic decimal CalculateFee(Order order) { if (order is null) return 0; if (!order.IsValid) return 0; if (order.Customer is null) return 0; return order.Total * 0.05m;}

3. Replace Conditionals with Polymorphism

A long switch on a type discriminator is a classic smell. Each case adds 1 to the score and the method becomes the only place that needs to change whenever a new variant appears. Moving the per-variant behaviour into derived classes (Strategy, Template Method, or simple subclass overrides) typically collapses the central method to CC = 1.

4. Use Modern C# Pattern Matching and Switch Expressions

Switch expressions are still counted by analyzers, but they encourage flatter, side-effect-free code than chains of nested ifs. Combined with pattern matching, they often replace a CC of 8-10 with a single, declarative expression:

| | public static decimal DiscountFor(Customer c) => c switch { { IsVip: true, YearsActive: >= 5 } => 0.20m, { IsVip: true } => 0.10m, { YearsActive: >= 10 } => 0.08m, { YearsActive: >= 1 } => 0.03m, _ => 0m}; | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

5. Use Lookup Tables for Pure Mappings

If a method is mostly a list of “input X maps to output Y”, a Dictionary<TKey, TValue> or a static readonly array is almost always preferable to a chain of ifs. The dictionary lookup is CC = 1, regardless of how many entries it holds.

6. Boolean Parameters Are a Code Smell

A method that takes several bool flags almost always hides several methods in a trench coat. Splitting SendEmail(bool html, bool urgent, bool dryRun) into more specific methods reduces both the per-method complexity and the chance that a caller passes the wrong combination of flags.

Frequently Asked Questions

What is a good Cyclomatic Complexity score in C#?

Below 10 is considered safe, between 10 and 20 needs attention, above 25 is what Microsoft’s CA1502 rule flags as excessive. McCabe’s original recommendation, still widely quoted, is to refactor any method that exceeds 10.

Does the else keyword increase Cyclomatic Complexity?

No. The else branch is already implied by its matching if, so it does not add a new independent path. Only the if itself counts.

Does a switch statement count once or per case?

Per case (and per default). The switch keyword itself does not increment the score. A switch with 6 cases plus a default contributes 7 to the method’s Cyclomatic Complexity.

Does Cyclomatic Complexity equal the number of unit tests I need?

It is a useful lower bound, not a contract. Cyclomatic Complexity gives you the number of linearly independent paths, which is the minimum number of tests required for full path coverage. In practice you may need fewer (some paths are infeasible) or more (data combinations within a single path can still misbehave).

What is the difference between Cyclomatic Complexity and Cognitive Complexity?

Cyclomatic Complexity measures the number of paths. Cognitive Complexity, popularised by SonarSource, also weighs nesting depth and boolean operator combinations because they make code harder to read even when they do not add new paths. The two metrics are complementary: low cyclomatic, high cognitive is rare; low cognitive, high cyclomatic is also rare; methods that are bad on one are usually bad on the other.

How do I measure Cyclomatic Complexity in Visual Studio?

In Visual Studio, go to Analyze > Calculate Code Metrics > For Solution. The resulting window lists Cyclomatic Complexity per method, type and project. For continuous enforcement, enable the CA1502 analyzer and configure its threshold via a CodeMetricsConfig.txt additional file.

Does Cyclomatic Complexity work on async or LINQ code?

Yes. The C# compiler rewrites async/await into a state machine, but most tools (NDepend, the Roslyn analyzers, Visual Studio’s metrics) compute Cyclomatic Complexity on the source-level method. await itself does not count, but the if, while and catch constructs surrounding it do. LINQ query operators are method calls, which do not count either, but lambdas passed to them are analyzed as their own methods.

Conclusion

Cyclomatic Complexity is one of the oldest code metrics still in active use, and the reason is simple: it captures something that maps directly to real-world pain. A high score means more paths to reason about, more tests to write, more places where a bug can hide. Keeping it under control, especially on the methods that change the most, pays for itself within weeks.

That said, the score on its own is half the picture. A complex but heavily tested method is rarely the one that wakes you up at night; a moderately complex method with zero coverage that gets touched every sprint will. Pair Cyclomatic Complexity with coverage (or with the CRAP score), enforce a delta-based rule rather than a flat threshold on legacy code, and use IL Cyclomatic Complexity to sanity-check the libraries you depend on. Combined, these techniques turn a 1970s metric into a surprisingly modern early-warning system for technical debt.

If you want to try this on your own codebase, download a free trial of NDepend and run an analysis: the complexity hotspots usually become obvious within the first few minutes.

This article is brought to you by the team behind NDepend — a proven .NET static analysis tool for improving code maintainability, security, and overall quality. Whether you’re modernizing a legacy .NET application or starting fresh in C#, get started with your free full-featured trial today!