skip to content

Give a concrete example of a smart-cast or type-inference case that K2 handles better than the old compiler.

level: seniorimportance: should knowfreq 25%

answer

  1. Consistency, not new syntax
  2. Smart cast through local val aliases
  3. Survives ||/&&/!/early-return guards
  4. Some unsound code now rejected
  5. Cause = unified FIR data-flow

basics

~20 s

K2 tracks the types of values more carefully, so smart casts work in more places — for example after combining conditions with logical operators or across local variables — where the old compiler sometimes gave up.

solid answer

~50 s

K2's unified FIR-based data-flow analysis tracks smart-cast information more precisely, so more correct casts are allowed and a few unsound ones are now rejected. Concrete wins: smart casts propagate through local val aliases (assign a nullable to a val, check that val, and the original is smart-cast too in cases the old compiler missed); smart casts survive across boolean combinations and inverted conditions (using `&&`, `||`, `!`, and early returns) more consistently; and smart casts on properties captured/checked across `is`/`!= null` branches behave uniformly. On the inference side, K2 improves builder-style inference and handling of `when` branch result types, and is stricter about genuinely ambiguous overloads. The right framing in an interview: K2 didn't add a new keyword — it made the existing `?.`, `is`, `!= null`, and smart-cast machinery behave consistently because all phases now read the same FIR data-flow graph.

code

kotlin · 10 lines
kotlin
fun firstChar(s: String?): Char? {
    if (s == null || s.isEmpty()) return null
    // smart cast to non-null String survives the combined guard
    return s[0]
}

fun describe(x: Any?) {
    val y = x
    if (y is String) println(y.length) // y smart-cast to String
}

go deeper

for a junior

Recognizes smart casts and that K2 makes them work in more places.

for a middle

Gives a concrete example (combined guards / val alias) and knows var/cross-module props aren't smart-castable.

for a senior

Explains the cause (unified FIR data-flow) and that some unsound code is now correctly rejected with the right fix.

for a principal

Plans a migration: audits newly-rejected sites, categorizes them (unsound cast vs ambiguous overload), and sets a remediation policy.

## Background terms - **Smart cast**: after you prove a value's type (e.g. `x is String` or `x != null`), Kotlin lets you use it *as* that type without an explicit cast. The compiler does this via **data-flow analysis** — tracking what is known about each value at each program point. - **Type inference**: the compiler computing types you didn't write (e.g. the result type of a `when`). K2 didn't introduce new syntax here. It made these *existing* mechanisms (`is`, `!= null`, `?.`, `as?`) behave **consistently**, because in K2 all phases read the same FIR data-flow graph instead of the old scattered structures. ## Example 1 — smart cast through a local val alias ```kotlin fun describe(x: Any?) { val y = x if (y is String) { // K2 reliably smart-casts y to String here println(y.length) } } ``` K2's data-flow tracking across simple aliases is more dependable; the old compiler had corner cases (especially with nullability and captured values) where it would refuse. ## Example 2 — smart casts across boolean logic and early return ```kotlin fun firstChar(s: String?): Char? { if (s == null || s.isEmpty()) return null // after the guard, s is smart-cast to non-null String return s[0] } ``` Combining conditions with `||`/`&&`, negation `!`, and early `return` and still keeping the smart cast on the following statements is handled uniformly in K2. ## Example 3 — `when` result-type inference ```kotlin fun pick(flag: Boolean): Number = when (flag) { true -> 1 // Int false -> 2L // Long } // inferred common supertype is consistent under K2 ``` K2 computes the common branch type more consistently. ## The flip side — newly rejected unsound code Because analysis is more correct, a handful of programs the old compiler *wrongly* allowed now fail to compile. Common categories: relying on a smart cast that wasn't actually sound (e.g. an `open`/`var` property that could change), or an overload that was genuinely **ambiguous** but happened to resolve before. These are surfaced bugs, not regressions; the fix is an explicit cast or disambiguation. ## What to say in an interview 1. K2 improves *consistency* of smart casts and inference, not new keywords. 2. The cause is the unified FIR data-flow model read by all phases. 3. Trade-off: a few previously-accepted unsound programs are now correctly rejected.

  • Why might code that compiled on Kotlin 1.9 fail to compile on 2.0?
    K2 is more correct: it rejects unsound smart casts and genuinely ambiguous overloads the old compiler wrongly accepted. The fix is an explicit cast or disambiguation, not reverting K2.
  • Does a smart cast work on a mutable `var` property from another module?
    No — it could change between the check and use, so it's not stably smart-castable; this is true in both compilers, and K2 enforces it consistently.

saying these in an interview costs you the question

  • Claiming K2 added new smart-cast keywords or operators
  • Saying smart casts now work on any mutable property
  • Treating newly-rejected code as a K2 bug rather than a surfaced unsoundness
  • Confusing smart cast with explicit `as` casting
  • Asserting inference results changed arbitrarily rather than became consistent

context