skip to content

Why can ktlintFormat succeed while ktlintCheck still reports violations on the same code?

level: middleimportance: should knowfreq 40%

answer

  1. Format fixes only what it can
  2. Two classes of rule, not two configs
  3. Some rules only measure and report
  4. A rename is a semantic change
  5. standard:function-naming cannot autocorrect

basics

~20 s

Not every ktlint rule can autocorrect. Rules that need a human decision, such as naming rules and max-line-length, only report; the format task rewrites what it can and leaves those violations in place, so the check task still fails on them.

solid answer

~50 s

ktlint splits its rules into ones that can rewrite the source and ones that can only report. Whitespace-shaped rules — indentation, trailing spaces, final newline, import ordering — have a single mechanically correct output, so the format task fixes them. Rules whose fix is a **semantic choice** cannot: `standard:function-naming`, `standard:property-naming`, `standard:class-naming`, `standard:filename` and `standard:package-name` would have to invent an identifier, and `standard:max-line-length` would have to decide where a line should break. So a format run legitimately reports success while leaving those findings for the check task, which then fails the build. The right reaction is to read the rule id in the report and act on it — rename the symbol, restructure the line, or decide the rule is not this project's policy — not to run the format task again or assume config drift between the two tasks. They share the same rule engine and the same configuration.

code

console · 7 lines
console
$ ./gradlew ktlintFormat
BUILD SUCCESSFUL

$ ./gradlew ktlintCheck
src/main/kotlin/OrderMath.kt:7:9 Function name should start with a lowercase letter (standard:function-naming)
src/main/kotlin/OrderMath.kt:12:1 Exceeded max line length (standard:max-line-length)
BUILD FAILED

go deeper

for a junior

Remember that ktlint's format task fixes layout only. If the check task still fails afterwards, read the rule id it prints instead of re-running the format task.

for a middle

Explain the split: mechanical layout rules have one correct output and are rewritten, while naming and line-length rules can only report because the fix is a semantic or readability decision.

for a senior

Show the diagnostic path — identify the rule id, decide between fixing the code, changing project policy, or grandfathering — and explain why re-running the formatter or chasing caches wastes time.

for a principal

Own the build-ergonomics angle: which rule classes are allowed to block a merge, how failures surface rule ids to the developer, and how you avoid a gate that is neither auto-fixable nor clearly actionable.

## The symptom Someone runs the ktlint format task, watches it succeed, pushes, and the build fails on the ktlint check task pointing at a file the format task just touched. It looks like a bug, or like the two tasks disagree. Neither is true, and being able to say why is the point of this question. ## One engine, two modes ktlint parses Kotlin source into the compiler's syntax tree and runs a set of rules over it. Every rule can *report* a violation. Only some rules can additionally *fix* one, by rewriting nodes in that tree before the file is written back to disk. Check mode runs the rules and reports; format mode runs the same rules with the same configuration and additionally applies the fixes that exist. The configuration, the active rule set and the enabled/disabled state of each rule are identical between the two modes — that is why "they must be reading different config" is the wrong diagnosis. So the outcome of a format run is: *all autocorrectable violations are gone; all non-autocorrectable violations remain, and were reported to you*. A subsequent check run finds exactly that remainder. ## Why some rules cannot autocorrect The dividing line is whether the correction is mechanical or a judgment call. **Mechanical, so fixable.** There is exactly one right answer and no information is lost. Indentation depth, spacing around operators and keywords, trailing whitespace, a missing final newline, the order of imports, blank-line rules, curly-brace placement. The tool can compute the corrected text from the syntax tree alone. **A judgment call, so lint-only.** The tool would have to invent something a human owns: - Naming rules such as `standard:function-naming`, `standard:property-naming` and `standard:class-naming` would have to choose a new identifier. Any rename is also a semantic change that ripples through call sites, so a formatter must not attempt it. - `standard:filename` and `standard:package-name` would have to move or rename a file, which is a refactoring, not a formatting pass. - `standard:max-line-length` would have to decide *where* a long line should break, which is a readability decision. A very long string literal or a single long identifier cannot be broken at all. That last one deserves care, because it is where the naive mental model breaks. ktlint does have wrapping rules — argument-list wrapping, chain wrapping, general wrapping — that reformat long constructs, and when they are enabled they often bring a line back under the limit as a side effect. But `max-line-length` itself does not wrap anything; it measures. If the wrapping rules are disabled in a project's configuration, or the line is one unbreakable token, the length violation survives every format run you throw at it. ## How to react The report line names the rule id in parentheses, and that id tells you which of three actions is correct: 1. **Fix the code by hand.** Rename the function, split the line, move the file. This is right when the rule expresses a standard the project actually holds. 2. **Turn the rule off in configuration.** Right when the rule encodes an opinion the project has deliberately rejected — for example a codebase that permits long comment lines, or test method names that would fail a naming rule. 3. **Grandfather it.** Right only while adopting ktlint on an existing codebase, and only for violations you intend to work through. What is *not* right is re-running the format task, clearing caches, or bisecting configuration, all of which are the common time sinks when the candidate believes the format task should have fixed everything. ## Why teams hit this The convention "format first, then check" makes the format task feel like a guarantee, and for a while it is: a codebase that only ever violates whitespace rules is fully auto-fixable, so the first non-autocorrectable finding is a surprise. It usually appears when a project turns on more opinionated rules, or when someone writes a test method or a DSL builder whose name is intentionally unconventional. Knowing the two classes of rule up front means the failure reads as expected behaviour rather than tooling flakiness — and it is why a build should surface the rule id in its output rather than just a pass/fail. ## What interviewers listen for The strong answer states the autocorrectable/lint-only split as a property of the rules, gives at least one concrete lint-only rule id, and explains the underlying principle: a formatter may rewrite layout, never meaning. The weak answer treats the format task as "fix everything" and looks for an environmental explanation.

  • A long line survives every format run. What are you actually looking at?
    `standard:max-line-length` only measures; it never wraps. Wrapping is done by separate rules such as argument-list wrapping and chain wrapping, and if those are disabled — or the line is a single long string literal or identifier that cannot be split — the length violation persists no matter how many times you run the format task. Fix it by hand or accept the rule is not for this project.
  • A test method name fails a ktlint naming rule and the build is blocked. What are the legitimate options?
    Three: rename the method, suppress the rule on that declaration with a `@Suppress` carrying the qualified rule id, or turn the rule off in configuration if the project deliberately writes test names that way. Re-running the format task is not an option — the rule cannot autocorrect. Prefer the narrowest choice that reflects the real intent.

A spell-checker can silently fix a typo, but it cannot rename your variable for you — the first has one right answer, the second is a decision only the author can make.

saying these in an interview costs you the question

  • Assumes the format task fixes every ktlint violation
  • Blames a stale Gradle cache or config drift
  • Claims check and format run different rule sets
  • Says max-line-length auto-wraps long lines
  • Adds a baseline entry rather than renaming the symbol

context