Refactoring with Cognitive Complexity
Understand SonarQube's Cognitive Complexity metric and how using it as a guide during refactoring produces code that is measurably easier to read and reason about.
Introduction to Cognitive Complexity
Anne Campbell, the primary author of cognitive complexity, presented a comprehensive overview of this modern code quality metric during a 45-minute webinar hosted by SonarSource. Unlike traditional complexity measurements that rely on mathematical formulas, cognitive complexity introduces a human-centered approach to understanding code difficulty. The metric was developed to address a significant gap in existing code quality tools: while cyclomatic complexity effectively measures the number of test cases needed for coverage, it fails to capture the true understandability of code from a developer's perspective.
Why Cognitive Complexity Was Needed
To illustrate the limitations of cyclomatic complexity, Campbell presented two contrasting methods: sum_of_primes and get_words. Both methods possessed identical cyclomatic complexity scores of four, yet their actual understandability differs dramatically. The sum_of_primes method contains nested loops and conditional statements, while get_words uses a straightforward switch statement with multiple cases. Despite equal cyclomatic scores, the sum_of_primes method is objectively harder to understand. This disconnect revealed the need for a metric grounded in guiding principles rather than mathematical formulas—one that accounts for how developers actually perceive code complexity based on control flow and readability.
Core Principles and Calculation Rules
Cognitive complexity operates on three foundational guiding principles. First, it ignores structures that allow multiple statements to be readably shorthanded into one statement, such as well-named methods and syntactic sugar like null coalescing operators. Second, it increments for each break in linear code flow—the ideal being that code should read like a novel from top to bottom, left to right. Third, it accounts for nesting by incrementing more heavily when flow-breaking structures are nested within each other, recognizing that nested logic requires greater cognitive effort to parse.
The specification for calculating cognitive complexity increments the score for specific control flow structures: breaks in linear flow (if/else, ternary operators, catch, switch statements), loops (for, while, do-while), recursion, jumps to labels (goto, break, continue), and sequences of binary logical operators. Nesting multipliers are applied to these same structures when they appear within other control structures. Importantly, cognitive complexity excludes hard language features such as pointers, templates, and generics—areas where determining complexity thresholds becomes subjective and language-dependent.
Practical Application and Validation
When cognitive complexity was applied to the earlier examples, the results proved illuminating. The sum_of_primes method received a cognitive complexity score of seven: plus one for the initial for loop, plus two for the nested for loop (accounting for nesting), plus three for the nested if statement (accounting for multiple nesting levels), and plus one for the continue statement. The get_words method, by contrast, scored only one for its switch statement and cases. These numerical differences immediately reveal which code requires greater cognitive effort to understand, validating the metric's alignment with human perception.
The presentation also addressed language-specific considerations, establishing exceptions where necessary. COBOL programmers, for instance, rely on workarounds due to the absence of elsif structures, and penalizing them for these language limitations would be unfair. Similarly, JavaScript and Python received specific exceptions for pseudo-class structures and decorators respectively. These language-specific adjustments ensure that cognitive complexity remains a fair measurement across diverse programming ecosystems while maintaining consistency in how control flow complexity is assessed.
Key Takeaways
- Cognitive complexity measures understandability, not just test coverage, by focusing on how developers perceive code difficulty through control flow patterns
- Nested structures receive higher complexity scores than sequential ones, reflecting the increased cognitive load required to mentally track multiple levels of conditional or loop logic
- The metric ignores syntactic sugar and well-named methods while strictly measuring control flow, keeping the focus on actual code readability challenges
- Language-specific exceptions are applied judiciously to prevent penalizing developers for language limitations while maintaining metric fairness across different programming languages
- Cyclomatic complexity and cognitive complexity serve different purposes; teams seeking to measure developer understanding should adopt cognitive complexity alongside or instead of cyclomatic complexity