Why is chaining multiple !! on one line (a!!.b!!.c) considered bad practice, and how does it affect debugging?
answer
- stack trace shows the line, not which !! threw
- split into one assertion per line
- or use ?. ... ?: error("...")
- chain of !! = fighting the type system
- consider making intermediates non-null by construction
basics
~10 sIf you put several !! on one line and it crashes, the error only tells you the line number, not which !! actually failed. Splitting them out makes the failure point obvious.
solid answer
~40 sWhen you write `a!!.b!!.c!!` on a single line, any of the three assertions could throw the NullPointerException, but the stack trace reports only the line, not the column or which sub-expression was null. That ambiguity slows debugging. The fix is either to split the assertions across separate statements so each failure maps to a distinct line, or — better — to replace the chain with null-aware handling such as `a?.b?.c` plus an Elvis fallback, or a single `checkNotNull` with a descriptive message at the right spot. Multiple !! on a line is also a strong smell that the code is fighting nullability instead of modelling it; it usually indicates the types or the data flow should be reconsidered.
go deeper
Recognizes that one !! per line is clearer and that chained !! makes errors harder to read.
Explains the stack-trace ambiguity and rewrites the chain with checkNotNull-per-line or ?./?: handling.
Identifies chained !! as a design smell and proposes making intermediates non-null by construction at the boundary.
Connects it to API/data-modelling strategy and lint enforcement so such chains never enter the codebase.
## The problem ```kotlin val result = config!!.database!!.url!! ``` If this throws, the JVM stack trace points at this **line**. But three different things could have been null: `config`, `config.database`, or `config.database.url`. The trace usually does not disambiguate which `!!` fired, so you must add logging or a debugger to find out — friction that defeats the 'fail fast and clearly' goal of an assertion. ## Why it happens Developers often add `!!` reactively to silence each nullability error the compiler raises, ending up with a chain. Each `!!` is an *unproven claim*; stacking them multiplies the runtime risk on a single line. ## Better approaches **1. One assertion per line (if assertions are truly warranted):** ```kotlin val cfg = checkNotNull(config) { "config not loaded" } val db = checkNotNull(cfg.database) { "database section missing" } val url = checkNotNull(db.url) { "db.url missing" } ``` Each failure now names the exact missing piece, in both the message and the line number. **2. Model the nullability with safe calls + Elvis:** ```kotlin val url = config?.database?.url ?: error("Database URL is not configured") ``` `?.` short-circuits the whole chain to `null` the moment any link is null, and `?:` turns that into a single, well-described failure. **3. Reconsider the types.** A chain of `!!` often signals that intermediate values should be non-null by construction (e.g. parsed/validated once at the boundary into a non-null model), so the inner code never deals with `T?` at all. ## Key takeaway A chained `!!` trades a small amount of typing now for large debugging cost later and hides which invariant broke. Prefer null-aware operators or one well-described assertion per fact.
- How does a?.b?.c differ from a!!.b!!.c when b is null?The safe-call chain short-circuits and the whole expression evaluates to null (no exception); the !! chain throws a NullPointerException at the failing assertion.
- What's a structural fix that removes the need for the chain entirely?Validate/parse the data once at the boundary into a non-null domain model so downstream code works with non-null types and never needs !!.
saying these in an interview costs you the question
- Sees no problem with a!!.b!!.c!! style code
- Claims the stack trace always pinpoints the exact failing !!
- Thinks safe-call chaining and !! chaining behave identically
- Adds !! purely to silence compiler errors without thought
- Never considers fixing the underlying types