skip to content

Write an idiomatic expression that returns the length of `input: Any` if it is a `String`, and `-1` otherwise, using `as?`. Explain why `as?` is preferred over an `is` check plus `as` here.

level: middleimportance: must knowfreq 70%

answer

  1. (x as? T)?.member ?: default
  2. as? folds test + cast into one operator
  3. is gives smart-cast; is+as is redundant
  4. Prefer when(x){is A...} for multi-branch
  5. Add ?.let when reusing the cast value

basics

~10 s

Write (input as? String)?.length ?: -1. as? does the type test and conversion in one step and gives null on mismatch, so you don't need a separate is check followed by a cast.

solid answer

~40 s

The idiomatic one-liner is `(input as? String)?.length ?: -1`. `input as? String` produces a `String?` (the string if the type matches, `null` otherwise); `?.length` is the safe call returning `Int?`; `?:` supplies the `-1` fallback. This is preferred over `if (input is String) input.length else -1` mainly for conciseness and expression-orientation — `as?` collapses the test-and-cast into a single operator that flows naturally into `?.` and `?:`. The `is` version is also perfectly valid and uses smart-casts, so it's not wrong; for a single branch returning a default, the `as?`/Elvis chain is more compact and reads as 'typed-or-default'. Prefer `is` when you have multiple branches per type or want exhaustive `when (x) { is A -> ...; is B -> ... }` handling.

code

kotlin · 5 lines
kotlin
fun lengthOrMinusOne(input: Any): Int =
    (input as? String)?.length ?: -1

lengthOrMinusOne("abc") // 3
lengthOrMinusOne(3.14)  // -1

go deeper

for a junior

Produces the (x as? String)?.length ?: -1 chain and knows each operator's role.

for a middle

Explains the trade-off vs is/smart-cast and recognizes the is+as redundancy.

for a senior

Chooses between expression chains and when based on number of branches and readability, avoiding exception-driven control flow.

for a principal

Pushes type discrimination into sealed types/polymorphism so cast chains stay at boundaries, treating pervasive as? as a design smell.

## The typed-or-default idiom The canonical pattern combines three null-safety operators: ```kotlin fun lengthOrMinusOne(input: Any): Int = (input as? String)?.length ?: -1 ``` Step by step: 1. **`input as? String`** — safe cast. Type `String?`. Yields the string if `input` is actually a `String`, else `null`. 2. **`?.length`** — the **safe-call operator**. If the receiver is non-null, calls `length`; if `null`, short-circuits to `null`. Type `Int?`. 3. **`?: -1`** — the **Elvis operator**. Returns the left side if non-null, otherwise the right-hand default `-1`. ### Why `as?` instead of `is` + `as` Compare the explicit form: ```kotlin fun lengthOrMinusOne(input: Any): Int = if (input is String) input.length else -1 // smart-cast inside the if ``` This is equally correct. Inside the `if (input is String)` branch, Kotlin **smart-casts** `input` to `String`, so no explicit cast is needed. The difference is style: - `as?` + `?.` + `?:` is a compact **expression** that reads as 'treat as String, or default'. Great for a single fallback. - `is` + smart-cast is clearer when you branch differently per type, especially with `when`: ```kotlin val r = when (input) { is String -> input.length is Collection<*> -> input.size else -> -1 } ``` ### Anti-pattern: `is` then `as` ```kotlin if (input is String) (input as String).length else -1 // redundant cast ``` The explicit `as String` here is **redundant** because smart-casting already narrowed the type; tools like detekt flag it. So the real choice is `as?`/Elvis vs `is`/smart-cast — never `is` followed by a manual `as`. ### Combining with `let` When you need the cast value more than once, `?.let` keeps it readable: ```kotlin (input as? String)?.let { it.length + it.hashCode() } ?: -1 ```

  • Why is `(input as String)` redundant inside `if (input is String) { ... }`?
    The `is` check smart-casts `input` to `String` within the branch, so the variable is already typed `String`; the explicit cast adds nothing and is flagged as redundant.
  • When would you prefer `when (x) { is A -> ...; is B -> ... }` over `as?`?
    When you need distinct logic per multiple types, or exhaustive handling of a sealed hierarchy — `as?` chains only express one 'or default' branch cleanly.

saying these in an interview costs you the question

  • Writing `is` then a manual `as` (redundant cast)
  • Using `as` without a null/exception handling path
  • Forgetting the result of `as?` is nullable and skipping `?.`/`?:`
  • Catching ClassCastException instead of using `as?`
  • Claiming `as?` cannot be combined with smart-casting at all

context