skip to content

What is the difference between `x as String?` and `x as? String`? When `x` is `null` or the wrong type, how does each behave?

level: middleimportance: should knowfreq 40%

answer

  1. as String? = unsafe cast to nullable; throws on wrong non-null type
  2. as? String = safe cast; null on wrong type, never throws
  3. Both return null for null input
  4. Differ only on non-null wrong type: throw vs null
  5. ? on operator (as?) ≠ ? on target type (String?)

basics

~20 s

x as String? is an unsafe cast to a nullable type: it allows null but still throws if x is a non-null wrong type. x as? String is a safe cast: it returns null for both null input and wrong types, never throwing.

solid answer

~40 s

`x as String?` is an **unsafe cast to a nullable type**. It succeeds when `x` is a `String` or when `x` is `null`, but it still throws `ClassCastException` if `x` is a non-null value of a different type (e.g. an `Int`). `x as? String` is the **safe cast operator**: it returns the value when the type matches, and `null` in every other case — both for `null` input and for a non-null type mismatch — and never throws. So they agree on `null` input and on a matching `String`, but diverge on a non-null wrong type: `as String?` throws, `as?` returns `null`. Choose `as?` when a type mismatch is an expected, recoverable case; choose `as String?` only when `null` is legal but a wrong non-null type is a bug you want surfaced.

code

kotlin · 9 lines
kotlin
fun demo(x: Any?) {
    val viaTarget: String? = try { x as String? } catch (e: ClassCastException) { "THROW" }
    val viaSafe: String? = x as? String
    println("$viaTarget | $viaSafe")
}

demo(null) // null | null
demo("hi") // hi | hi
demo(42)   // THROW | null

go deeper

for a junior

Recognizes as? is the safe one but may conflate as String? with it.

for a middle

Cleanly separates operator-? from target-? and states the throw-vs-null divergence on wrong types.

for a senior

Chooses between fail-fast (as String?) and recover (as? String) based on whether a wrong type is a bug or expected.

for a principal

Uses the distinction deliberately at API boundaries to encode invariants — null allowed, wrong type rejected loudly.

## `as String?` vs `as? String` These look similar but the `?` lives in different places and means different things. ### `x as String?` — unsafe cast, nullable target Here `String?` is the **target type** of an ordinary unsafe `as` cast. The cast considers `null` an acceptable value of `String?`, so: - `x == null` → succeeds, result is `null`. - `x` is a `String` → succeeds. - `x` is a **non-null wrong type** (e.g. `Int`) → throws **`ClassCastException`**. ```kotlin val a: Any? = null val b: Any? = "hi" val c: Any? = 42 a as String? // null (ok) b as String? // "hi" (ok) c as String? // throws ClassCastException ``` ### `x as? String` — safe cast Here `?` is part of the **operator** `as?`. It returns `null` whenever the cast can't produce a `String`: - `x == null` → `null`. - `x` is a `String` → the string. - `x` is a non-null wrong type → `null` (no throw). ```kotlin a as? String // null b as? String // "hi" c as? String // null (no exception) ``` ### Side-by-side | input | `as String?` | `as? String` | |-------|--------------|--------------| | `null` | `null` | `null` | | `"hi"` | `"hi"` | `"hi"` | | `42` (wrong type) | **throws** | `null` | Both produce a `String?` static type. The only behavioral difference is the wrong-non-null-type row. ### Choosing - Use **`as? String`** when a wrong type is an *expected* outcome you handle (typically with `?:`). - Use **`as String?`** when `null` is a legitimate value but a non-null value of the wrong type indicates a *bug* you want to fail fast on. ### Common confusion Don't read `as String?` as 'safe cast' — the safety token is `as?`, not a trailing `?` on the type. Misplacing the `?` silently changes throw-vs-null behavior.

  • Do `as String?` and `as? String` ever behave identically?
    Yes — for `null` input and for an actual `String`, both yield the same result. They differ only when `x` is a non-null value of the wrong type.
  • Which would you use to fail fast on an unexpected non-null type while allowing null?
    `as String?` — it permits `null` but throws `ClassCastException` on a non-null wrong type, surfacing the bug.

saying these in an interview costs you the question

  • Saying `as String?` never throws (it does, on wrong non-null types)
  • Treating the trailing `?` on the type as making the cast safe
  • Claiming `as? String` throws on null input
  • Thinking the two are fully interchangeable

context