A detekt rule fires constantly on your codebase — how do you decide between tuning, scoping, and disabling it?
answer
- Read the findings before changing anything
- Prefer the narrowest lever available
- Rule options, then paths, then numbers
- Turning it off is a boundary decision
- Every override needs a recorded reason
basics
~20 sFirst 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.
solid answer
~50 sI start by reading a sample of the findings, because the volume alone tells you nothing — a hundred true positives and a hundred false ones look identical in a report. If they are real, the rule stays and the code changes. If the rule is right in principle but wrong for a category of code, I use the narrowest lever that fits: the rule's own options first (`MagicNumber.ignorePropertyDeclaration`, `LongParameterList.ignoreDataClasses`, `TooManyFunctions.ignorePrivate`), then an `excludes` list of path globs for test or generated sources, then the numeric limit if our idiom genuinely differs from the default. `active: false` is the last resort and is only honest when the rule contradicts a convention we chose on purpose — for instance a formatting rule whose ownership sits with another tool. Every override gets a comment in `detekt.yml` saying why, so the file reads as a set of decisions rather than a scar record.
code
yaml · 10 linesstyle:
MagicNumber:
active: true
# Literal fixtures are the point in tests; constants stay covered.
excludes: ['**/test/**']
ignorePropertyDeclaration: true
ignoreNamedArgument: true
# Import hygiene is owned by the formatter in this pipeline.
WildcardImport:
active: falsego deeper
Know that a noisy rule is usually configured, not deleted, and that detekt.yml is shared code — a change there affects everyone, so it belongs in review.
Explain the available levers concretely: per-rule ignore options, excludes path globs, numeric limits, and active: false, and which kind of noise each one is meant for.
Demonstrate the triage: sample the findings, classify true versus idiom-driven, choose the narrowest lever, and keep the number you commit to. Be ready to describe a rule you deliberately turned off and why that was a boundary decision.
Own the standard as policy: who may change detekt.yml, how an exception is recorded and revisited, and how you keep the configuration from drifting into a monument of quiet opt-outs across many teams.
## Why the question is asked A linter configuration is a written record of a team's craft standard, and the fastest way to destroy it is the reflex fix: the build went red, so the rule goes off. Interviewers ask this to find out whether you treat detekt's configuration as a design artefact or as an obstacle. The mature answer is a ladder of increasingly blunt levers, applied in order, with the blunt end used rarely and explicitly. ## Step 0: classify the findings Read twenty of them before touching anything. There are only three outcomes. The findings are **true** — the rule is doing its job and the code owes a change, possibly spread over several commits. The findings are **true but not worth acting on** in this code — for example magic numbers in a test fixture, where literal values are the point. Or the findings are **false for this idiom** — a rule written against a Java-shaped assumption firing on idiomatic Kotlin. Only the last two justify a configuration change, and they justify different ones. ## Lever 1: the rule's own options Most detekt rules ship with switches that encode legitimate exceptions, and reaching for them first is what separates a tuned config from a gutted one. `MagicNumber` alone carries `ignoreNumbers`, `ignorePropertyDeclaration`, `ignoreConstantDeclaration`, `ignoreCompanionObjectPropertyDeclaration`, `ignoreNamedArgument`, `ignoreEnums`, `ignoreRanges`, `ignoreHashCodeFunction` and `ignoreExtensionFunctions`. `LongParameterList` has `ignoreDataClasses` and `ignoreDefaultParameters`. `TooManyFunctions` has `ignorePrivate`, `ignoreInternal`, `ignoreOverridden` and `ignoreDeprecated`. Each of these keeps the rule enforcing its actual intent while removing a category of noise, which is exactly what you want: the signal survives. ## Lever 2: scope by path When the exception is a *place* rather than a *shape*, use the rule's `excludes` list of path globs — `excludes: ['**/test/**']` is the archetype. Test sources, generated code and Gradle script files are the usual candidates. This is narrower than disabling because production code stays covered, and it is far better than sprinkling annotations across hundreds of files. Note that a rule's `excludes`/`includes` are path patterns; the `excludes` list under the top-level `config` block is a different thing entirely — it exempts configuration *properties* from validation. ## Lever 3: move the number For the metric rules, changing the limit is legitimate when the codebase's idiom genuinely differs from the default — exhaustive `when` dispatch over a sealed hierarchy inflating cyclomatic complexity, or constructor injection inflating parameter counts. The test is whether you can state the number you can hold and then hold it. Raising a limit once with a recorded reason is a standard. Raising it every time the build goes red is an abandoned rule that still costs CI time. ## Lever 4: switch it off `active: false` is honest in one situation: the rule contradicts a convention the team chose deliberately. The clearest example is double-reporting — when another tool in the pipeline already owns a concern, leaving both enabled produces two findings for one problem and, worse, two sources of truth that can disagree. Turning one off is then a boundary decision, not an evasion. Say that in the config: an `active: false` with a comment explaining who owns the concern instead reads completely differently from a bare one. ## Keeping the configuration honest Three habits make this durable. **Comment every override** with the reason, in the `detekt.yml` itself. **Leave configuration validation on** (`config: validation: true`) so a misspelled property fails the run instead of silently doing nothing — a mistyped key is the quietest way to think you have a standard you do not have. And **review configuration changes like code**: a diff that turns a rule off deserves the same scrutiny as a diff that deletes a test, because that is structurally what it is. One-off exceptions do not belong here at all. A single justified violation is an `@Suppress` on that declaration with a comment; changing the project-wide configuration to accommodate one function inverts the cost.
- Why leave detekt's configuration validation enabled?With `config: validation: true`, detekt fails the run when `detekt.yml` names a property or rule it does not recognise. Without it, a typo such as `ignorePropertyDeclarations` silently does nothing and the team believes it has an exception it never got. Validation turns a silent misconfiguration into a build error you can see.
- When is @Suppress the right answer instead of a configuration change?When the exception is a single justified decision rather than a pattern. One function that legitimately holds protocol offsets gets `@Suppress("MagicNumber")` with a comment; the project-wide configuration stays untouched. Changing shared configuration to accommodate one call site pushes the cost onto everyone and hides the decision from review.
saying these in an interview costs you the question
- Setting active: false the moment the build goes red
- Excluding whole modules to silence a handful of findings
- Raising a limit repeatedly instead of picking one to hold
- Leaving overrides in detekt.yml with no comment
- Assuming a large finding count means the rule is wrong