skip to content

Detekt

Static analysis for Kotlin beyond formatting: code-smell rules, complexity and size metrics, custom rule authoring, and baseline files that freeze existing debt. Asked because it is how a team expresses its craft standards mechanically, and because tuning thresholds rather than disabling rules is the mature answer.

on this pageshow

explore

questions

4

Which code metrics does detekt's complexity rule set measure, and what are its default limits?

level: middleimportance: must knowfreq 55%

answer

  1. Counting rules, not correctness rules
  2. Branching, lines, parameters, member counts
  3. One of the two complexity rules is off by default
  4. Roughly 60 lines a function, 600 a class
  5. Keys renamed to allowed* in detekt 2.0

basics

~10 s

detekt's complexity set measures branching and size: CyclomaticComplexMethod and CognitiveComplexMethod per function, LongMethod and LargeClass in lines, NestedBlockDepth, ComplexCondition, LongParameterList, TooManyFunctions. Defaults sit near 60 lines per function and 600 per class.

solid answer

~40 s

The `complexity` rule set is detekt's numeric layer. Per function it measures **branching** — `CyclomaticComplexMethod` (default allowed complexity 14) and the optional `CognitiveComplexMethod`, which is off by default and penalises *nested* control flow more heavily — plus `ComplexCondition` for boolean operators in a single condition and `NestedBlockDepth` (default 4). Per size it measures `LongMethod` (60 lines) and `LargeClass` (600 lines). Per shape it measures `LongParameterList` (5 for functions, 6 for constructors, data classes ignored by default), `TooManyFunctions` (11 per file, class, interface, object or enum, tests excluded), plus the off-by-default `MethodOverloading` and `ComplexInterface`. Every rule carries options that encode legitimate idioms, so the interesting work is choosing the number your codebase can actually hold rather than accepting or deleting the rule.

code

yaml · 14 lines
yaml
complexity:
  CyclomaticComplexMethod:
    allowedComplexity: 18
  LongMethod:
    allowedLines: 70
  NestedBlockDepth:
    allowedDepth: 4
  LongParameterList:
    allowedFunctionParameters: 5
    allowedConstructorParameters: 8
    ignoreDataClasses: true
  TooManyFunctions:
    allowedFunctionsPerClass: 15
    ignorePrivate: true

go deeper

for a junior

Recall that the complexity set counts branches, lines, parameters and members, and that the numbers live in detekt.yml. Naming three rules and roughly where the defaults sit is enough here.

for a middle

Explain what each metric actually counts and why Kotlin-specific options exist — scope functions in the cyclomatic count, data classes in the parameter list, test excludes on function counts.

for a senior

Show that you have set these numbers for a real codebase: which idioms forced a change, how you kept the limit stable afterwards, and why a green complexity report is a floor rather than a design review.

for a principal

Own the standard across teams: which metrics are non-negotiable, which are advisory, and how a limit gets changed — by argued decision with a recorded reason, not by whoever hits the red build first.

## What this rule set is for detekt's `complexity` rule set is the part that counts things. It does not reason about correctness; it measures shape — how much branching a function contains, how long it is, how many parameters and members a declaration carries — and reports whatever exceeds a configured number. That makes it the most opinionated and most argued-about part of a detekt configuration, and the reason interviewers ask about it: the numbers encode a team's craft standard. ## Branching metrics, per function **`CyclomaticComplexMethod`** counts independent paths through a function: each `if`, `when` branch, loop, `catch`, and each `&&`/`||` adds one. The default allows a complexity of 14. It is deliberately generous — a function past that is usually a state machine that wants decomposition. The rule has options that matter in Kotlin specifically: `ignoreSingleWhenExpression`, `ignoreSimpleWhenEntries`, `ignoreNestingFunctions` and a `nestingFunctions` list (`let`, `run`, `apply`, `also`, `with`, `use`, `forEach`), because idiomatic Kotlin chains scope functions in ways a Java-shaped metric over-penalises. **`CognitiveComplexMethod`** implements a different metric: instead of weighting every branch equally, it adds a penalty proportional to nesting depth and discounts constructs that read linearly. Three sequential `if`s score far less than three nested ones, which matches how a reader actually experiences the code. It is **inactive by default**, so a team that wants it must switch it on explicitly. **`ComplexCondition`** flags a single boolean expression built from too many operators (default: more than three), and **`NestedBlockDepth`** flags control structures nested past the allowed depth (default 4). These two catch the readability problems the path count alone can miss. ## Size and shape metrics **`LongMethod`** and **`LargeClass`** count source lines — 60 and 600 by default. They are crude on purpose: a long function is not automatically wrong, but it is always worth a second look. **`LongParameterList`** allows 5 parameters on a function and 6 on a constructor, and by default ignores data classes, since a data class *is* a parameter list. `ignoreDefaultParameters` and an `ignoreAnnotatedParameter` list cover injection and framework-driven signatures. **`TooManyFunctions`** counts declarations per container — file, class, interface, object and enum each get their own allowance, 11 by default — and excludes test sources by default, because a test class legitimately holds many small functions. Its `ignorePrivate`, `ignoreInternal`, `ignoreOverridden` and `ignoreDeprecated` switches decide what counts as public surface. Two more ship inactive: **`MethodOverloading`** (too many overloads of one name) and **`ComplexInterface`** (too many declarations in one interface). ## How the limits are spelled This is version-sensitive and worth stating explicitly in an interview. detekt 1.23 spells these limits with `threshold`-style keys — `LongMethod: threshold: 60`, `TooManyFunctions: thresholdInFiles: 11` — with the semantics *report when the metric reaches the threshold*. detekt 2.0 renames them to intent-revealing keys that express what is **allowed**: `allowedLines`, `allowedComplexity`, `allowedDepth`, `allowedConditions`, `allowedFunctionParameters`, `allowedConstructorParameters`, `allowedFunctionsPerFile` / `PerClass` / `PerInterface` / `PerObject` / `PerEnum`. Because the meaning shifted from *threshold reached* to *maximum permitted*, re-read the numbers when you upgrade rather than copying them across blindly. ## Using the numbers well The defaults are a starting position, not a verdict. A codebase full of exhaustive `when` dispatch over a sealed hierarchy will trip cyclomatic complexity for a structure that is genuinely readable; a Spring-style constructor will trip the parameter list. The mature move is to pick a number the team can hold and then hold it — raising a limit once, with a comment explaining the codebase's idiom, is a standard; raising it every time the build goes red is an unmaintained rule. Equally, these metrics are a smoke detector, not a design review. Passing every complexity rule says nothing about coupling, naming or module boundaries. Cite them as a floor under review, never as a substitute for it.

  • How does CognitiveComplexMethod differ from CyclomaticComplexMethod, and why would you enable it?
    Cyclomatic complexity weights every branch equally, so three sequential `if`s score the same as three nested ones. Cognitive complexity adds a penalty that grows with nesting depth and discounts constructs that read linearly, which tracks reading difficulty far better. It is inactive by default, so enabling it is a deliberate choice — often alongside a *lower* number than the cyclomatic one.
  • Why does LongParameterList ignore data classes by default?
    A data class is essentially a named parameter list: the constructor's job is to carry fields, and splitting it would just move the same data behind another type. detekt encodes that with `ignoreDataClasses: true`. The companion options `ignoreDefaultParameters` and `ignoreAnnotatedParameter` exist for the same reason — framework-driven signatures are not the design smell the rule is hunting.
  • Do any complexity rules exclude test sources out of the box?
    `TooManyFunctions` ships with test-source path globs in its `excludes` list, because a test class legitimately holds many small functions. Most other complexity rules apply everywhere by default, so if long test setup functions are a deliberate style you scope them yourself with the rule's own `excludes` list rather than turning the rule off.

saying these in an interview costs you the question

  • Calling every complexity finding a bug to fix
  • Assuming cognitive complexity is on by default
  • Thinking lines of code and cyclomatic complexity measure the same thing
  • Copying 1.23 threshold numbers into 2.0 allowed* keys unchanged
  • Treating a green complexity report as a passed design review

context

open as a page

How do you suppress a single detekt finding in Kotlin source without editing detekt.yml?

level: juniorimportance: should knowfreq 50%

basics

~20 s

Annotate the smallest declaration that contains the finding with Kotlin's @Suppress, passing detekt's rule id — for example @Suppress("MagicNumber") on one function. detekt reuses the standard annotation; a configured alias or the rule set id also works.

open as a page

A detekt rule fires constantly on your codebase — how do you decide between tuning, scoping, and disabling it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

First ask whether the findings are real. If the rule is right but over-broad, narrow it with its own options or an excludes path glob; move the number only if the codebase's idiom genuinely differs; set active: false only when the rule contradicts a deliberate convention.

open as a page

Why does detekt's autoCorrect option leave most findings unfixed in a Gradle build?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

Because autoCorrect only reaches rules that implement a correction. Most detekt rules describe a design problem with no mechanical fix — a long method or a magic number cannot be rewritten automatically — so they stay reported no matter how the flag is set.

open as a page