skip to content

When would you choose !! over the safe-call ?. or the Elvis ?: operator, and what is the cost of choosing wrong?

level: middleimportance: must knowfreq 70%

answer

  1. null normal? -> ?. / ?: ; null = bug? -> assert
  2. !! throws NPE, checkNotNull/requireNotNull throw with a message
  3. ?. yields null, ?: yields fallback
  4. wrong !! = self-inflicted runtime crash
  5. silent ?: can hide a real bug

basics

~20 s

Use !! only when you are certain a value can't be null and a crash is the right outcome if you're wrong. Use ?. or ?: when null is a real possibility you want to handle gracefully.

solid answer

~40 s

The three operators express three different intents. `?.` (safe call) says 'do this only if non-null, otherwise yield null'. `?:` (Elvis) says 'use this fallback if the left side is null'. `!!` says 'this is never null; if it is, that's a bug — crash now'. So `!!` is correct only when null represents a broken invariant rather than a normal case, and an immediate, loud failure (a NullPointerException with a stack trace) is the response you want. The cost of choosing `!!` wrongly is a runtime crash that the compiler could have prevented — you have traded Kotlin's compile-time guarantee for a deferred exception. Often a cleaner alternative is `checkNotNull(x) { "message" }` or `requireNotNull(x)`, which throw with a descriptive message instead of a bare NPE, documenting the invariant.

go deeper

for a junior

Can state that !! crashes while ?. and ?: handle null, and picks ?: for a fallback.

for a middle

Applies the 'normal possibility vs violated invariant' rule and knows checkNotNull/requireNotNull as better assertions.

for a senior

Discusses how silent ?: fallbacks can mask bugs and chooses the operator from domain semantics, not warning-silencing.

for a principal

Sets conventions: assertions carry messages, !! is banned in review, and APIs are shaped so null-as-invariant is rare.

## The three null operators, side by side | Operator | Reads as | On null it... | Result type of `x?...` | |---|---|---|---| | `x?.foo()` (safe call) | "call foo if x is non-null" | returns `null` | `R?` (nullable) | | `x ?: default` (Elvis) | "x, or default if null" | yields `default` | non-null if default is | | `x!!` (not-null assertion) | "x, which is never null" | throws `NullPointerException` | `T` (non-null) | ## Decision rule Ask: **is a null here a normal possibility, or a violated invariant?** - **Normal possibility** → handle it: `?.`, `?:`, `?.let { }`, or an explicit `if (x != null)` smart cast. - **Violated invariant** (can't happen unless there is a bug) → fail fast. `!!` is *one* way, but usually a worse one. ## Prefer intent-revealing failures over bare !! The standard library gives you clearer assertions: ```kotlin // Bare assertion: throws KotlinNullPointerException, no context val token = header!! // Better: documents the invariant and throws IllegalStateException with a message val token = checkNotNull(header) { "Authorization header must be set by the filter" } // For function arguments, requireNotNull throws IllegalArgumentException fun process(id: String?) { val realId = requireNotNull(id) { "id was null" } } ``` - `checkNotNull` → throws `IllegalStateException` (a *state* problem). - `requireNotNull` → throws `IllegalArgumentException` (a *caller-input* problem). - Both **smart-cast** the value to non-null and return it, so they read as drop-in replacements for `!!`. ## Cost of choosing wrong Choosing `!!` where null is actually possible converts a compile-time-preventable situation into a production `NullPointerException`. Choosing `?.`/`?:` where you should have asserted can *hide* a real bug behind a silent `null` or fallback, so the system limps along in a corrupt state. The skill is matching the operator to the semantics, not defaulting to one.

  • Why might checkNotNull(x) be preferable to x!! even though both 'crash on null'?
    checkNotNull lets you attach a message describing the violated invariant and throws IllegalStateException, making the failure self-explanatory in logs; !! throws a context-free NPE.
  • What is the downside of reflexively using ?: "" to silence nullability warnings?
    It can mask a genuine bug by substituting an empty/default value, letting the program continue in an invalid state instead of surfacing the problem.

saying these in an interview costs you the question

  • Treats !! as interchangeable with ?. with no semantic difference
  • Recommends !! as the default to 'get rid of the warning'
  • Doesn't know checkNotNull/requireNotNull exist
  • Claims ?: and !! both supply a fallback value
  • Can't articulate the 'normal vs invariant' distinction

context