{"article":{"slug":"understanding-cyclomatic-complexity","title":"Understanding Cyclomatic Complexity","subtitle":null,"summary":"Erik Dietrich explains cyclomatic complexity in C#: how McCabe’s metric counts independent paths, why scores above ~10 hurt readability and testing, and how to use it without cargo-culting thresholds.","content_type":"tutorial","language":"en","canonical_url":"https://blog.ndepend.com/understanding-cyclomatic-complexity/","author":{"name":"Erik Dietrich","url":"https://blog.ndepend.com/","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"NDepend","url":"https://blog.ndepend.com/","listing_slug":null,"listing":null},"topics":[{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"},{"name":"Engineering","slug":"engineering","url":"https://listedarticles.com/topics/engineering"},{"name":"C","slug":"c","url":"https://listedarticles.com/topics/c"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":3093,"reading_minutes":13,"published_at":"2026-05-25T01:17:49.000Z","added_at":"2026-09-19T03:08:56.878Z","updated_at":"2026-09-19T03:08:56.878Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":false},"profile_url":"https://listedarticles.com/articles/understanding-cyclomatic-complexity","markdown_url":"https://listedarticles.com/articles/understanding-cyclomatic-complexity.md","example":false,"citation":"Erik Dietrich, NDepend. \"Understanding Cyclomatic Complexity.\" 25 May 2026. https://blog.ndepend.com/understanding-cyclomatic-complexity/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://blog.ndepend.com/understanding-cyclomatic-complexity/"},"body_markdown":"# Understanding Cyclomatic Complexity\n\n**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.\n\nThis 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.\n\n## What is Cyclomatic Complexity?\n\nCyclomatic 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:\n\n| 1 | M = E - N + 2P | \n\nwhere *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.\n\nWhat 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.\n\n## Definition of Cyclomatic Complexity in C#\n\nThe Cyclomatic Complexity for a C# method is concretely 1 + {the number of following expressions found in the body of the method}:\n\n| 1 2 3 4 5 | if  while  for  foreach case  default  continue goto  &&  \\|\\|  catch ternary operator ?:  ?? and or | \n\nThe following expressions are **not** counted for CC computation:\n\n| 1 2 3 4 5 | else  do  switch  try  using throw  finally  return object creation method call field access | \n\nTwo 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.\n\n## Example of Cyclomatic Complexity Impact in C#\n\n### Exhibiting a Complex Method\n\nHere 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:\n\n| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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.\");       }    } } | \n\nEight 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.\n\n### Refactoring the Complex Method in Several Simpler Methods\n\nThe method above can be refactored into several less complex methods. In the code we use CC to refer to each method Cyclomatic Complexity score:\n\n| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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.\");       }    } } | \n\n### Benefits of Refactoring\n\n- **Simpler Control Flow:** The main method now delegates specific tasks to smaller, more focused methods.\n- **Easier to Test:** You can test each smaller method independently.\n- **Lower Cyclomatic Complexity:** The complexity is spread across multiple methods, making each method easier to understand and maintain independently.\n- **Better Naming:** Method names like`IsValidOrder` or`ApplyDiscountIfEligible` document intent, so a reader does not have to mentally simulate the body to understand the high-level flow.\n\nNote 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.\n\n## Cyclomatic Complexity Thresholds: What Score Is Too High?\n\nThere 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:\n\n| Cyclomatic Complexity | Risk profile | Practical interpretation | \n|---|---|---|\n| 1 – 10 | Simple, low risk | McCabe’s original recommendation. Easy to test, easy to read. | \n| 11 – 20 | Moderately complex | Still manageable, but worth a second pair of eyes during review. | \n| 21 – 50 | Complex, high risk | Hard to test exhaustively. Strong refactoring candidate. | \n| > 50 | Untestable | Bug magnets. Often legacy hotspots that need to be broken down. | \n\nTwo 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.\n\nIn 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.\n\n## Measuring Cyclomatic Complexity in C#\n\nYou 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.\n\nNDepend 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.\n\nVisual 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.\n\n## Ruling C# Cyclomatic Complexity\n\nNDepend 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.\n\nYou 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:\n\n- From now, all methods added should respect basic quality principles\n- Avoid making complex methods even more complex\n\nThis 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:\n\n| 1 2 3 4 5 6 7 8 | // <Name>Cyclomatic Complexity got worse</Name> warnif count > 0 from  m in JustMyCode.Methods where   m.CodeWasChanged() &&   m.OlderVersion().CyclomaticComplexity < m.CyclomaticComplexity &&   m.OlderVersion().CyclomaticComplexity > 10 select new { m, OldComplexity = m.OlderVersion().CyclomaticComplexity, m.CyclomaticComplexity } | \n\nThe 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.\n\n## Visualizing C# Cyclomatic Complexity\n\nA colored treemap can be used to visualize the cyclomatic complexity of your C# methods. In this visualization, each rectangle represents a method:\n\n- The size of the rectangle corresponds to the number of statements in the method.\n- The color of the rectangle reflects the method’s cyclomatic complexity.\n\nThe 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.\n\n## C# Cyclomatic Complexity and Tests\n\nWriting 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.**\n\nBy 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.\n\nThe CRAP score defines a specific mathematical formula to combine complexity and coverage. Expressed in CQLinq, that formula is:\n\n| 1 2 3 4 5 6 7 8 | // <Name>CRAP</Name> from m in JustMyCode.Methods let CC = m.CyclomaticComplexity let uncov = (100 - m.PercentageCoverage) / 100f let CRAP = (CC * CC * uncov * uncov * uncov) + CC select new { m, CRAP } | \n\nThe 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.\n\n## Going Beyond Cyclomatic Complexity\n\nCyclomatic Complexity is a great *start* for reasoning about your code’s complexity, not the end of the conversation. Two extensions are worth knowing about.\n\n### Pair Complexity with Branch Coverage\n\nIf 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:\n\n| 1 2 3 4 5 | // <Name>Uncovered code in complex methods</Name> warnif count > 0 from  m in JustMyCode.Methods where m.CyclomaticComplexity > 10 && m.PercentageBranchCoverage < 100 select new { m, m.PercentageBranchCoverage, m.CyclomaticComplexity } | \n\nIt 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”.\n\n### IL Cyclomatic Complexity for Third-Party Code\n\nAny 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.\n\nThis 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.\n\n## How to Reduce Cyclomatic Complexity in C#\n\nRefactoring 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.\n\n### 1. Extract Method\n\nThe 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.\n\n### 2. Early Return / Guard Clauses\n\nInstead 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.\n\n| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // Nested public decimal CalculateFee(Order order) {    if (order != null) {       if (order.IsValid) {          if (order.Customer != null) {             return order.Total * 0.05m;          }       }    }    return 0; } // Guarded public 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; } | \n\n### 3. Replace Conditionals with Polymorphism\n\nA 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.\n\n### 4. Use Modern C# Pattern Matching and Switch Expressions\n\nSwitch expressions are still counted by analyzers, but they encourage flatter, side-effect-free code than chains of nested `if`s. Combined with pattern matching, they often replace a CC of 8-10 with a single, declarative expression:\n\n| 1 2 3 4 5 6 7 | 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 }; | \n\n### 5. Use Lookup Tables for Pure Mappings\n\nIf 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 `if`s. The dictionary lookup is CC = 1, regardless of how many entries it holds.\n\n### 6. Boolean Parameters Are a Code Smell\n\nA 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.\n\n## Frequently Asked Questions\n\n### What is a good Cyclomatic Complexity score in C#?\n\nBelow 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.\n\n### Does the `else` keyword increase Cyclomatic Complexity?\n\nNo. The `else` branch is already implied by its matching `if`, so it does not add a new independent path. Only the `if` itself counts.\n\n### Does a `switch` statement count once or per case?\n\nPer `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.\n\n### Does Cyclomatic Complexity equal the number of unit tests I need?\n\nIt 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).\n\n### What is the difference between Cyclomatic Complexity and Cognitive Complexity?\n\nCyclomatic 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.\n\n### How do I measure Cyclomatic Complexity in Visual Studio?\n\nIn 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.\n\n### Does Cyclomatic Complexity work on async or LINQ code?\n\nYes. 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.\n\n## Conclusion\n\nCyclomatic 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.\n\nThat 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.\n\nIf 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.","body_html":"<h1 id=\"understanding-cyclomatic-complexity\">Understanding Cyclomatic Complexity</h1>\n<p><strong>Cyclomatic Complexity</strong> (or <strong>CC</strong>) in C# is a code metric that counts the number of <strong>linearly independent execution paths</strong> through a method. Concretely, it is computed as 1 plus the number of branching constructs in the method body (such as <code>if</code>, <code>while</code>, <code>for</code>, <code>case</code>, <code>&amp;&amp;</code>, <code>||</code>, <code>?:</code> and <code>??</code>). 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.</p>\n<p>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.</p>\n<h2 id=\"what-is-cyclomatic-complexity\">What is Cyclomatic Complexity?</h2>\n<p>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 <em>control flow graph</em> where nodes are blocks of statements and edges are jumps between them. On that graph, the Cyclomatic Complexity is given by the classic formula:</p>\n<p>| 1 | M = E - N + 2P | </p>\n<p>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 <strong>1 + the number of decision points</strong>, which is the form most tools actually compute.</p>\n<p>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.</p>\n<h2 id=\"definition-of-cyclomatic-complexity-in-c\">Definition of Cyclomatic Complexity in C</h2>\n<p>The Cyclomatic Complexity for a C# method is concretely 1 + {the number of following expressions found in the body of the method}:</p>\n<p>| 1 2 3 4 5 | if  while  for  foreach case  default  continue goto  &amp;&amp;  ||  catch ternary operator ?:  ?? and or | </p>\n<p>The following expressions are <strong>not</strong> counted for CC computation:</p>\n<p>| 1 2 3 4 5 | else  do  switch  try  using throw  finally  return object creation method call field access | </p>\n<p>Two details that trip people up: <code>else</code> does not increment the score because the alternative path was already created by its matching <code>if</code>; and a <code>switch</code> contributes one unit per <code>case</code> (and one for <code>default</code>), not one for the <code>switch</code> keyword itself. C# pattern-matching constructs (<code>and</code>, <code>or</code>, the modern switch expression with patterns) also add to the score, the same way their classic counterparts do.</p>\n<h2 id=\"example-of-cyclomatic-complexity-impact-in-c\">Example of Cyclomatic Complexity Impact in C</h2>\n<h3 id=\"exhibiting-a-complex-method\">Exhibiting a Complex Method</h3>\n<p>Here is a complex method with entangled <code>if</code> and <code>else</code> scopes. The keyword <code>if</code> is used six times and <code>&amp;&amp;</code> is used once. Hence its Cyclomatic Complexity score is 8:</p>\n<p>| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | public static class OrderLogic {    public static void ProcessOrder(          int orderId,          bool isPriority,          bool isInternational,          bool isGift,          bool isCouponApplied,          decimal orderTotal) {       if (orderId &lt;= 0) {          Console.WriteLine(&quot;Invalid order ID.&quot;);          return;       }       if (isPriority) {          Console.WriteLine(&quot;Processing priority order.&quot;);          if (isInternational) {             Console.WriteLine(&quot;Processing international priority order.&quot;);             if (isGift) {                Console.WriteLine(&quot;This is a gift order.&quot;);             }          }       } else {          Console.WriteLine(&quot;Processing standard order.&quot;);          if (isInternational) {             Console.WriteLine(&quot;Processing international standard order.&quot;);          }       }       if (isCouponApplied &amp;&amp; orderTotal &gt; 100) {          Console.WriteLine(&quot;Applying discount for orders over $100.&quot;);       } else {          Console.WriteLine(&quot;No discount applicable.&quot;);       }    } } | </p>\n<p>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.</p>\n<h3 id=\"refactoring-the-complex-method-in-several-simpler-methods\">Refactoring the Complex Method in Several Simpler Methods</h3>\n<p>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:</p>\n<p>| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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 &lt;= 0) {          Console.WriteLine(&quot;Invalid order ID.&quot;);          return false;       }       return true;    }    private static void ProcessOrderType(          bool isPriority,          bool isInternational,          bool isGift) { // CC 5       if (isPriority) {          Console.WriteLine(&quot;Processing priority order.&quot;);          if (isInternational) {             Console.WriteLine(&quot;Processing international priority order.&quot;);          }       } else {          Console.WriteLine(&quot;Processing standard order.&quot;);          if (isInternational) {             Console.WriteLine(&quot;Processing international standard order.&quot;);          }       }       if (isGift) {          Console.WriteLine(&quot;This is a gift order.&quot;);       }    }    private static void ApplyDiscountIfEligible( // CC 3        bool isCouponApplied,        decimal orderTotal) {       if (isCouponApplied &amp;&amp; orderTotal &gt; 100) {          Console.WriteLine(&quot;Applying discount for orders over $100.&quot;);       } else {          Console.WriteLine(&quot;No discount applicable.&quot;);       }    } } | </p>\n<h3 id=\"benefits-of-refactoring\">Benefits of Refactoring</h3>\n<ul><li><strong>Simpler Control Flow:</strong> The main method now delegates specific tasks to smaller, more focused methods.</li><li><strong>Easier to Test:</strong> You can test each smaller method independently.</li><li><strong>Lower Cyclomatic Complexity:</strong> The complexity is spread across multiple methods, making each method easier to understand and maintain independently.</li><li><strong>Better Naming:</strong> Method names like<code>IsValidOrder</code> or<code>ApplyDiscountIfEligible</code> document intent, so a reader does not have to mentally simulate the body to understand the high-level flow.</li></ul>\n<p>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 <em>per method</em>, because that is the unit a developer has to reason about at a time.</p>\n<h2 id=\"cyclomatic-complexity-thresholds-what-score-is-too-high\">Cyclomatic Complexity Thresholds: What Score Is Too High?</h2>\n<p>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:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Cyclomatic Complexity</th><th>Risk profile</th><th>Practical interpretation</th></tr></thead><tbody><tr><td>1 – 10</td><td>Simple, low risk</td><td>McCabe’s original recommendation. Easy to test, easy to read.</td></tr><tr><td>11 – 20</td><td>Moderately complex</td><td>Still manageable, but worth a second pair of eyes during review.</td></tr><tr><td>21 – 50</td><td>Complex, high risk</td><td>Hard to test exhaustively. Strong refactoring candidate.</td></tr><tr><td>&gt; 50</td><td>Untestable</td><td>Bug magnets. Often legacy hotspots that need to be broken down.</td></tr></tbody></table></div>\n<p>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.</p>\n<p>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.</p>\n<h2 id=\"measuring-cyclomatic-complexity-in-c\">Measuring Cyclomatic Complexity in C</h2>\n<p>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.</p>\n<p>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.</p>\n<p>Visual Studio itself ships a “Calculate Code Metrics” command (<em>Analyze &gt; Calculate Code Metrics</em>) that reports Cyclomatic Complexity per method, type and assembly. The CA1502 analyzer can be wired into your build to actually <em>fail</em> 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.</p>\n<h2 id=\"ruling-c-cyclomatic-complexity\">Ruling C# Cyclomatic Complexity</h2>\n<p>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.</p>\n<p>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:</p>\n<ul><li>From now, all methods added should respect basic quality principles</li><li>Avoid making complex methods even more complex</li></ul>\n<p>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 <em>start growing</em>. Using NDepend’s CQLinq, you can express that idea directly:</p>\n<p>| 1 2 3 4 5 6 7 8 | // &lt;Name&gt;Cyclomatic Complexity got worse&lt;/Name&gt; warnif count &gt; 0 from  m in JustMyCode.Methods where   m.CodeWasChanged() &amp;&amp;   m.OlderVersion().CyclomaticComplexity &lt; m.CyclomaticComplexity &amp;&amp;   m.OlderVersion().CyclomaticComplexity &gt; 10 select new { m, OldComplexity = m.OlderVersion().CyclomaticComplexity, m.CyclomaticComplexity } | </p>\n<p>The query warns whenever an already-complex method (CC &gt; 10) becomes even more complex between two analysis snapshots. In other words, it surfaces the modifications that are <strong>actually risky</strong>, while leaving stable legacy untouched.</p>\n<h2 id=\"visualizing-c-cyclomatic-complexity\">Visualizing C# Cyclomatic Complexity</h2>\n<p>A colored treemap can be used to visualize the cyclomatic complexity of your C# methods. In this visualization, each rectangle represents a method:</p>\n<ul><li>The size of the rectangle corresponds to the number of statements in the method.</li><li>The color of the rectangle reflects the method’s cyclomatic complexity.</li></ul>\n<p>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.</p>\n<h2 id=\"c-cyclomatic-complexity-and-tests\">C# Cyclomatic Complexity and Tests</h2>\n<p>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, <strong>Cyclomatic Complexity provides a rough estimate of how many tests are required to fully test a method.</strong></p>\n<p>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.</p>\n<p>The CRAP score defines a specific mathematical formula to combine complexity and coverage. Expressed in CQLinq, that formula is:</p>\n<p>| 1 2 3 4 5 6 7 8 | // &lt;Name&gt;CRAP&lt;/Name&gt; from m in JustMyCode.Methods let CC = m.CyclomaticComplexity let uncov = (100 - m.PercentageCoverage) / 100f let CRAP = (CC * CC * uncov * uncov * uncov) + CC select new { m, CRAP } | </p>\n<p>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.</p>\n<h2 id=\"going-beyond-cyclomatic-complexity\">Going Beyond Cyclomatic Complexity</h2>\n<p>Cyclomatic Complexity is a great <em>start</em> for reasoning about your code’s complexity, not the end of the conversation. Two extensions are worth knowing about.</p>\n<h3 id=\"pair-complexity-with-branch-coverage\">Pair Complexity with Branch Coverage</h3>\n<p>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:</p>\n<p>| 1 2 3 4 5 | // &lt;Name&gt;Uncovered code in complex methods&lt;/Name&gt; warnif count &gt; 0 from  m in JustMyCode.Methods where m.CyclomaticComplexity &gt; 10 &amp;&amp; m.PercentageBranchCoverage &lt; 100 select new { m, m.PercentageBranchCoverage, m.CyclomaticComplexity } | </p>\n<p>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 &gt; 10”.</p>\n<h3 id=\"il-cyclomatic-complexity-for-third-party-code\">IL Cyclomatic Complexity for Third-Party Code</h3>\n<p>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.</p>\n<p>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.</p>\n<h2 id=\"how-to-reduce-cyclomatic-complexity-in-c\">How to Reduce Cyclomatic Complexity in C</h2>\n<p>Refactoring for lower Cyclomatic Complexity is mostly about <strong>extracting decisions out of the hot path</strong>. The techniques below come up over and over again on real codebases.</p>\n<h3 id=\"1-extract-method\">1. Extract Method</h3>\n<p>The most boring and the most effective. Pull a contiguous block of decisions into a well-named method, exactly like in the <code>ProcessOrder</code> example earlier. Each call site loses one chunk of complexity; each new method is small enough to test on its own.</p>\n<h3 id=\"2-early-return-guard-clauses\">2. Early Return / Guard Clauses</h3>\n<p>Instead of deeply nesting validation in <code>if</code> 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.</p>\n<p>| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // Nested public decimal CalculateFee(Order order) {    if (order != null) {       if (order.IsValid) {          if (order.Customer != null) {             return order.Total * 0.05m;          }       }    }    return 0; } // Guarded public 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; } | </p>\n<h3 id=\"3-replace-conditionals-with-polymorphism\">3. Replace Conditionals with Polymorphism</h3>\n<p>A long <code>switch</code> on a type discriminator is a classic smell. Each <code>case</code> 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.</p>\n<h3 id=\"4-use-modern-c-pattern-matching-and-switch-expressions\">4. Use Modern C# Pattern Matching and Switch Expressions</h3>\n<p>Switch expressions are still counted by analyzers, but they encourage flatter, side-effect-free code than chains of nested <code>if</code>s. Combined with pattern matching, they often replace a CC of 8-10 with a single, declarative expression:</p>\n<p>| 1 2 3 4 5 6 7 | public static decimal DiscountFor(Customer c) =&gt; c switch {    { IsVip: true, YearsActive: &gt;= 5 } =&gt; 0.20m,    { IsVip: true }                     =&gt; 0.10m,    { YearsActive: &gt;= 10 }              =&gt; 0.08m,    { YearsActive: &gt;= 1 }               =&gt; 0.03m,    _                                    =&gt; 0m }; | </p>\n<h3 id=\"5-use-lookup-tables-for-pure-mappings\">5. Use Lookup Tables for Pure Mappings</h3>\n<p>If a method is mostly a list of “input X maps to output Y”, a <code>Dictionary&lt;TKey, TValue&gt;</code> or a static readonly array is almost always preferable to a chain of <code>if</code>s. The dictionary lookup is CC = 1, regardless of how many entries it holds.</p>\n<h3 id=\"6-boolean-parameters-are-a-code-smell\">6. Boolean Parameters Are a Code Smell</h3>\n<p>A method that takes several <code>bool</code> flags almost always hides several methods in a trench coat. Splitting <code>SendEmail(bool html, bool urgent, bool dryRun)</code> into more specific methods reduces both the per-method complexity and the chance that a caller passes the wrong combination of flags.</p>\n<h2 id=\"frequently-asked-questions\">Frequently Asked Questions</h2>\n<h3 id=\"what-is-a-good-cyclomatic-complexity-score-in-c\">What is a good Cyclomatic Complexity score in C#?</h3>\n<p>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.</p>\n<h3 id=\"does-the-else-keyword-increase-cyclomatic-complexity\">Does the <code>else</code> keyword increase Cyclomatic Complexity?</h3>\n<p>No. The <code>else</code> branch is already implied by its matching <code>if</code>, so it does not add a new independent path. Only the <code>if</code> itself counts.</p>\n<h3 id=\"does-a-switch-statement-count-once-or-per-case\">Does a <code>switch</code> statement count once or per case?</h3>\n<p>Per <code>case</code> (and per <code>default</code>). The <code>switch</code> keyword itself does not increment the score. A switch with 6 cases plus a default contributes 7 to the method’s Cyclomatic Complexity.</p>\n<h3 id=\"does-cyclomatic-complexity-equal-the-number-of-unit-tests-i-need\">Does Cyclomatic Complexity equal the number of unit tests I need?</h3>\n<p>It is a useful lower bound, not a contract. Cyclomatic Complexity gives you the number of <em>linearly independent</em> 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).</p>\n<h3 id=\"what-is-the-difference-between-cyclomatic-complexity-and-cogniti\">What is the difference between Cyclomatic Complexity and Cognitive Complexity?</h3>\n<p>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 <em>read</em> 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.</p>\n<h3 id=\"how-do-i-measure-cyclomatic-complexity-in-visual-studio\">How do I measure Cyclomatic Complexity in Visual Studio?</h3>\n<p>In Visual Studio, go to <em>Analyze &gt; Calculate Code Metrics &gt; For Solution</em>. The resulting window lists Cyclomatic Complexity per method, type and project. For continuous enforcement, enable the CA1502 analyzer and configure its threshold via a <code>CodeMetricsConfig.txt</code> additional file.</p>\n<h3 id=\"does-cyclomatic-complexity-work-on-async-or-linq-code\">Does Cyclomatic Complexity work on async or LINQ code?</h3>\n<p>Yes. The C# compiler rewrites <code>async</code>/<code>await</code> into a state machine, but most tools (NDepend, the Roslyn analyzers, Visual Studio’s metrics) compute Cyclomatic Complexity on the source-level method. <code>await</code> itself does not count, but the <code>if</code>, <code>while</code> and <code>catch</code> 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.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>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.</p>\n<p>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.</p>\n<p>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.</p>","headings":[{"level":1,"text":"Understanding Cyclomatic Complexity","id":"understanding-cyclomatic-complexity"},{"level":2,"text":"What is Cyclomatic Complexity?","id":"what-is-cyclomatic-complexity"},{"level":2,"text":"Definition of Cyclomatic Complexity in C","id":"definition-of-cyclomatic-complexity-in-c"},{"level":2,"text":"Example of Cyclomatic Complexity Impact in C","id":"example-of-cyclomatic-complexity-impact-in-c"},{"level":3,"text":"Exhibiting a Complex Method","id":"exhibiting-a-complex-method"},{"level":3,"text":"Refactoring the Complex Method in Several Simpler Methods","id":"refactoring-the-complex-method-in-several-simpler-methods"},{"level":3,"text":"Benefits of Refactoring","id":"benefits-of-refactoring"},{"level":2,"text":"Cyclomatic Complexity Thresholds: What Score Is Too High?","id":"cyclomatic-complexity-thresholds-what-score-is-too-high"},{"level":2,"text":"Measuring Cyclomatic Complexity in C","id":"measuring-cyclomatic-complexity-in-c"},{"level":2,"text":"Ruling C# Cyclomatic Complexity","id":"ruling-c-cyclomatic-complexity"},{"level":2,"text":"Visualizing C# Cyclomatic Complexity","id":"visualizing-c-cyclomatic-complexity"},{"level":2,"text":"C# Cyclomatic Complexity and Tests","id":"c-cyclomatic-complexity-and-tests"},{"level":2,"text":"Going Beyond Cyclomatic Complexity","id":"going-beyond-cyclomatic-complexity"},{"level":3,"text":"Pair Complexity with Branch Coverage","id":"pair-complexity-with-branch-coverage"},{"level":3,"text":"IL Cyclomatic Complexity for Third-Party Code","id":"il-cyclomatic-complexity-for-third-party-code"},{"level":2,"text":"How to Reduce Cyclomatic Complexity in C","id":"how-to-reduce-cyclomatic-complexity-in-c"},{"level":3,"text":"1. Extract Method","id":"1-extract-method"},{"level":3,"text":"2. Early Return / Guard Clauses","id":"2-early-return-guard-clauses"},{"level":3,"text":"3. Replace Conditionals with Polymorphism","id":"3-replace-conditionals-with-polymorphism"},{"level":3,"text":"4. Use Modern C# Pattern Matching and Switch Expressions","id":"4-use-modern-c-pattern-matching-and-switch-expressions"},{"level":3,"text":"5. Use Lookup Tables for Pure Mappings","id":"5-use-lookup-tables-for-pure-mappings"},{"level":3,"text":"6. Boolean Parameters Are a Code Smell","id":"6-boolean-parameters-are-a-code-smell"},{"level":2,"text":"Frequently Asked Questions","id":"frequently-asked-questions"},{"level":3,"text":"What is a good Cyclomatic Complexity score in C#?","id":"what-is-a-good-cyclomatic-complexity-score-in-c"},{"level":3,"text":"Does the else keyword increase Cyclomatic Complexity?","id":"does-the-else-keyword-increase-cyclomatic-complexity"},{"level":3,"text":"Does a switch statement count once or per case?","id":"does-a-switch-statement-count-once-or-per-case"},{"level":3,"text":"Does Cyclomatic Complexity equal the number of unit tests I need?","id":"does-cyclomatic-complexity-equal-the-number-of-unit-tests-i-need"},{"level":3,"text":"What is the difference between Cyclomatic Complexity and Cognitive Complexity?","id":"what-is-the-difference-between-cyclomatic-complexity-and-cogniti"},{"level":3,"text":"How do I measure Cyclomatic Complexity in Visual Studio?","id":"how-do-i-measure-cyclomatic-complexity-in-visual-studio"},{"level":3,"text":"Does Cyclomatic Complexity work on async or LINQ code?","id":"does-cyclomatic-complexity-work-on-async-or-linq-code"},{"level":2,"text":"Conclusion","id":"conclusion"}]}}