Reject Double Bang in Generated Kotlin
Every double bang in AI-generated Kotlin is a crash waiting for prod. The !! operator converts any value to a non-nullable type — and if the value is null at that moment, it throws a NullPointerException right there. Kotlin's docs list !! as one of only three ways an NPE can happen in pure Kotlin code. When an AI assistant sprinkles it through generated code, it's not asserting knowledge — it's silencing the compiler because that's the shortest token path to code that compiles.
Why generated code loves !!
Code generators optimize for "compiles on the first try." Faced with a nullable type, the two honest options — propagate the nullability or handle the null branch — both cost more code. !! costs two characters. The same shortcut appears as the initialization-phase nullable field: a var user: User? = null that "will definitely be set before anyone reads it." That promise lives in a comment or in the author's head, not in the type system, so every read site either handles a null that "can't happen" or double-bangs it. Either way, absence has been converted from a compile-time fact into a runtime bet.
Fix 1: make absence explicit and handle it
If a value can genuinely be missing, keep the type nullable and use the operators the language gives you:
// AI draft — deferred crash
val name = response.user!!.name
// Explicit — compiler-checked
val name = response.user?.name ?: "anonymous"
The safe call ?. returns null instead of throwing; the Elvis operator ?: supplies the fallback. Chains compose: bob?.department?.head?.name is null if any link is null — no branch of that expression can throw an NPE.
Fix 2: model closed states with sealed results
When "null" is standing in for a state — not loaded yet, loaded, failed — a nullable field is the wrong tool. A sealed hierarchy names each state, and because all direct subclasses of a sealed class are known at compile time, when over it is exhaustive with no else:
sealed interface LoadState {
data object Loading : LoadState
data class Ready(val user: User) : LoadState
data class Failed(val cause: Exception) : LoadState
}
fun render(s: LoadState) = when (s) {
LoadState.Loading -> showSpinner()
is LoadState.Ready -> showUser(s.user)
is LoadState.Failed -> showError(s.cause)
// no else — the compiler proves every case is covered
}
Add a fourth state next month and every when that forgot it becomes a compile error, not a 3 a.m. page. That is the payoff: the compiler forces you to handle the empty case, and whole classes of crashes become unreachable.
Rules of thumb for review
| You see in the diff | Ask for instead |
|---|---|
foo!!.bar | foo?.bar + ?: fallback, or fix the type upstream |
var x: T? = null set "later" during init | Constructor injection, or a sealed Loading/Ready state |
lateinit var read before a guaranteed assignment | ::x.isInitialized guard — or restructure so init is ordered |
when on a sealed type with an else branch | Exhaustive when, no else, so new cases fail the build |
| Unannotated value from a Java API | Explicit Kotlin type on the public signature |
Power-user notes
lateinitis not a free pass. Reading it before assignment throwsUninitializedPropertyAccessException— a nicer name for the same crash. Use it only where a framework genuinely assigns before use (DI, test setup), and guard rare races with::prop.isInitialized.- Platform types are hidden double bangs. Values coming from Java have unknown nullability; Kotlin's coding conventions say public functions and properties initialized from platform types should declare their Kotlin type explicitly. Do it at the boundary, once, instead of
!!at ten call sites. - Make the ban executable. Tell your assistant in its project instructions: "Never emit
!!; prefer?./?:or sealed results." Generators follow stated constraints far better than they infer taste — and your reviewers stop repeating the same comment.
Resources
- Null safety — Kotlin docs:
?.,?:, and why!!is one of the three NPE causes - Sealed classes and interfaces — Kotlin docs: closed hierarchies and exhaustive
when - Late-initialized properties — Kotlin docs:
lateinit, its exception, andisInitialized - Coding conventions: platform types — Kotlin docs
Building with AI-generated code? Yeda AI designs, audits, and ships production LLM systems.