Explain the scoring rules of SonarSource's Cognitive Complexity metric: what adds points, what adds nothing, and how the nesting penalty works.
answer
- 3 rules: ignore shorthand / +1 per flow break / +1 per nesting level
- switch = +1 total, cases free
- else & else-if: flat +1, no nesting bonus
- boolean runs: && run +1, mixing adds more
- lambdas and catch raise nesting; default threshold 15
basics
~20 sEach structure that breaks straight-line reading — if, else, loops, catch, switch, jumps — adds one point, plus one more for each level of nesting it sits inside. Shorthand that condenses code (a whole switch, the method declaration itself) adds nothing extra.
solid answer
~50 sThree rules. (1) **No increment for shorthand**: a method declaration is free, and an entire `switch` costs +1 no matter how many cases, because the reader grasps it as one construct. (2) **+1 per break in linear flow**: every `if`, `else if`, `else`, ternary, `switch`, `for`/`while`/`do`, each `catch` clause, jumps to labels (`break label`, `goto`), recursion, and each *run* of like binary boolean operators — `a && b && c` is +1, `a && b || c` is +2. (3) **Nesting increment**: a flow-breaking structure also gains +1 per enclosing nesting level. Nesting level is raised by if/else/ternary, switch, loops, catch, and nested function or lambda bodies. Note the asymmetry: `else` and `else if` get the flat +1 but no nesting increment, since they continue an already-understood decision rather than adding depth. SonarQube's default threshold is 15 per function.
code
pseudocode · 10 linesif (a) { // +1 (nesting 0)
for (x in xs) { // +2 = 1+1 (nesting 1)
if (b && c) { // +3 = 1+2 (nesting 2)
... // +1 for the '&&' run
} else if (d) { // +1 flat: no nesting increment
...
}
}
}
// total = 1 + 2 + 3 + 1 + 1 = 8go deeper
State the three rules in plain words and give the switch-versus-nested-ifs contrast.
Score a small snippet correctly, including the else/else-if asymmetry and the boolean-run rule.
Explain why the rules were chosen (working-memory model), note lambdas and recursion, and give the metric's blind spots.
Treat the exact numbers as secondary; discuss threshold policy on new versus legacy code, exclusions, and the risk of optimising the score instead of the design.
## Purpose Cognitive Complexity (G. Ann Campbell, SonarSource, 2017) scores a function by how much effort a human spends following its control flow. Unlike cyclomatic complexity it is a heuristic, tuned to match intuition rather than derived from graph theory. ## Rule 1 — ignore shorthand Structures that let a reader collapse many lines into a single concept cost nothing extra: - The **method/function declaration** itself: +0. - A **whole `switch`**: +1 total, regardless of case count, because all cases are read as one dispatch. - **Null-coalescing / safe-navigation** shorthand (`a ?: b`, `a?.b`): +0. ## Rule 2 — +1 for each break in linear flow Add 1 for each of: | Construct | Increment | |---|---| | `if`, `else if`, `else`, ternary `?:` | +1 each | | `switch` | +1 (whole statement) | | `for`, `foreach`, `while`, `do-while` | +1 | | each `catch` clause (the `try` itself is +0, `finally` is +0) | +1 | | jump to a label: `break label`, `continue label`, `goto` | +1 | | **sequence** of like binary logical operators | +1 per run | | recursion (direct or mutual) | +1 | **Boolean sequences** are the subtle one. The metric charges per *run of the same operator*, because mixing operators is what forces you to think about precedence: - `a && b && c && d` → +1 (one run of `&&`) - `a && b || c` → +2 (a run of `&&`, then a run of `||`) - `a && b && c || d || e && f` → +3 A plain `return`, an assignment, and a normal (unlabelled) `break`/`continue` inside its own loop are **not** scored. ## Rule 3 — nesting increment When a flow-breaking structure appears inside other structures, it also gains **+1 per level of nesting**. Structures that **raise the nesting level** for whatever is inside them: `if`/`else`/`else if`/ternary, `switch`, all loops, `catch`, and **nested function or lambda bodies**. Structures that get the flat +1 but **no nesting increment**: `else`, `else if`, and jumps to labels — an `else` continues a decision the reader has already absorbed rather than adding a new level of context. ### Worked example ``` function f(xs, flag) { // +0 declaration if (flag) { // +1 (nesting 0) for (x in xs) { // +2 (+1, nesting 1) if (x.a && x.b) { // +3 (+1, nesting 2) ... +1 for the && run try { g(x) } catch (e) { // +4 (+1, nesting 3) if (e.fatal) throw e // +5 (+1, nesting 4) } } else if (x.c) { // +1 (else if: flat, no nesting bonus) h(x) } } } return 0 // +0 } ``` Total = 1 + 2 + 3 + 1(`&&`) + 4 + 5 + 1(`else if`) = **17**, over SonarQube's default per-function threshold of **15**. ## Practical notes and edge cases - **Lambdas raise nesting**, so a callback pyramid is penalised much like nested ifs — deliberate, since it reads the same way. - **Recursion costs +1** even though the code looks linear, because the reader must build a mental stack. - SonarQube reports cognitive complexity per function, aggregated per file/project, and its default rule (`S3776` in most languages) flags functions above 15; the threshold is configurable, and teams often set 10–15 for new code. - The metric is **language-adapted**: each analyser maps its language's constructs onto the three rules, so pattern matching, `when`, comprehensions and coroutines are scored by analogy rather than by a universal table. - It is a *heuristic*: two functions scoring 12 can differ wildly in real difficulty, and it cannot see naming, side effects, or a bad abstraction.
- Why does `a && b || c` score higher than `a && b && c`?Cognitive complexity charges +1 per run of the same operator. A uniform chain is one run (+1); mixing operators creates two runs (+2), reflecting the extra effort of reasoning about precedence and grouping.
- Why does a nested lambda increase the nesting level?Because a reader following the outer flow must push the enclosing context onto their mental stack to read the callback body — exactly as with a nested loop or if. Callback pyramids read like conditional pyramids, so they are scored alike.
- Does `try` add to the score?No. The `try` block and `finally` add nothing; each `catch` clause adds +1 plus its nesting increment, because each catch is a distinct alternate flow the reader must consider.
saying these in an interview costs you the question
- Saying each switch case adds a point (that is cyclomatic complexity)
- Counting one point per `&&`/`||` rather than per run of the same operator
- Giving `else if` a nesting penalty
- Believing a plain `return` or an ordinary unlabelled `break` adds to the score
- Presenting cognitive complexity as an objective measurement rather than a tuned heuristic