When is !! genuinely justified, and how would you set a team policy and lint rules around it?
answer
- justified: invariant true but compiler can't prove it
- prefer lateinit / smart cast / by lazy over !!
- checkNotNull/requireNotNull with a message beats bare !!
- detekt UnsafeCallOnNullableType + baseline in CI
- confine !! to boundaries, ban in business logic
basics
~20 s!! is fine in narrow cases where a value is guaranteed non-null but the compiler can't see it, like right after a check or a framework-initialized field. As a team, ban it by default and require a comment or a clearer assertion when it's truly needed.
solid answer
~50 sLegitimate uses of !! are narrow: a value the compiler can't prove non-null but that an invariant guarantees — e.g. a framework-injected field, a value just placed into a map and immediately read back, or interop where you have certain external knowledge. Even then, `checkNotNull`/`requireNotNull` with a message usually read better. Some test code uses !! pragmatically since a wrong assumption simply fails the test loudly. For policy, default to banning bare !! and steer developers toward smart casts, ?., ?:, lateinit (for non-null properties initialized after construction), and delegated properties. Enforce via detekt's UnsafeCallOnNullableType rule (and related ktlint/IDE inspections), allow exceptions only with a justifying comment, and review every !! as an explicit risk decision. The strategic goal is to keep !! out of business-logic interiors and confine the few survivors to boundaries with documented invariants.
go deeper
Can say !! is for cases you're sure aren't null, and that there are safer defaults like ?. and ?:.
Names lateinit, smart casts, and checkNotNull as alternatives and knows !! belongs at boundaries, not everywhere.
Lists concrete legitimate cases, prefers message-carrying assertions, and knows lint tooling exists to police !!.
Defines and automates the policy: detekt UnsafeCallOnNullableType with a baseline, justification comments, and a 'non-null inward' design discipline.
## When !! is defensible `!!` is justified only where **an invariant guarantees non-null but the compiler cannot prove it**: - **Framework/DI-initialized fields** the compiler sees as nullable but the lifecycle guarantees are set (often better solved with `lateinit var`). - **Put-then-get on a mutable map** within the same scope: `map[k] = v; val x = map[k]!!`. - **Interop** where external documentation guarantees non-null but annotations are missing. - **Tests**, pragmatically: a wrong `!!` just fails the test loudly, which is acceptable in test code. In most of these, a message-carrying assertion is still better: ```kotlin val session = checkNotNull(sessionHolder.current) { "current session must be set by the auth filter" } ``` ## Better tools that remove the need for !! - **`lateinit var`** — declare a non-null `var` initialized after construction; accessing it before init throws `UninitializedPropertyAccessException` (a clearer failure than a generic NPE), and you avoid `T?` + `!!` everywhere. - **Smart casts** — `if (x != null) { x.foo() }` narrows `x` to non-null with no operator. - **`?.let { }`** — run a block only when non-null. - **Delegated properties** (`by lazy`, custom delegates) — guarantee initialization. - **`?:` with `error(...)`** — `x ?: error("...")` asserts with a message and a meaningful exception. ## Team policy 1. **Default: ban bare `!!`.** Treat each one as an explicit, reviewed risk. 2. **Require justification.** Permit it only with a comment stating the invariant, or replace with `checkNotNull`/`requireNotNull` carrying that message. 3. **Confine to boundaries.** No `!!` in core business logic; push null handling to seams (config parsing, interop, DI). 4. **Automate enforcement.** detekt ships **`UnsafeCallOnNullableType`** which flags `!!`; you can set its severity and weave a baseline for legacy code. IntelliJ also flags redundant/unsafe assertions. Wire these into CI so new `!!` cannot merge silently. 5. **Track and burn down.** Use a baseline so existing `!!` are visible debt and net-new ones are blocked. ## Strategic framing The target state is a codebase where nullability is modelled in types, validated once at the edges, and non-null inward — so `!!` survives only as a rare, commented, boundary-level assertion. That is a design and culture goal, enforced by lint, not a per-developer judgement call made under deadline.
- How does lateinit var avoid the need for !! on a late-initialized property?It declares the property as a non-null var initialized after construction; you use it directly as non-null. Accessing it before init throws UninitializedPropertyAccessException, a clearer error than a bare NPE.
- Which detekt rule flags !! and how do you handle a large legacy codebase?UnsafeCallOnNullableType flags the not-null assertion; you generate a detekt baseline so existing occurrences are grandfathered while net-new ones fail the build.
saying these in an interview costs you the question
- Claims !! should never appear under any circumstances (too absolutist, ignores valid boundary cases)
- Has no automated enforcement, relies purely on code review memory
- Doesn't know lateinit as the idiomatic alternative for late-init non-null fields
- Cannot name any detekt/lint rule for !!
- Allows !! freely in core business logic