skip to content

When is !! genuinely justified, and how would you set a team policy and lint rules around it?

level: principalimportance: nice to knowfreq 25%

answer

  1. justified: invariant true but compiler can't prove it
  2. prefer lateinit / smart cast / by lazy over !!
  3. checkNotNull/requireNotNull with a message beats bare !!
  4. detekt UnsafeCallOnNullableType + baseline in CI
  5. 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 s

Legitimate 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

for a junior

Can say !! is for cases you're sure aren't null, and that there are safer defaults like ?. and ?:.

for a middle

Names lateinit, smart casts, and checkNotNull as alternatives and knows !! belongs at boundaries, not everywhere.

for a senior

Lists concrete legitimate cases, prefers message-carrying assertions, and knows lint tooling exists to police !!.

for a principal

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

context