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.
answer
- (x as? T)?.member ?: default
- as? folds test + cast into one operator
- is gives smart-cast; is+as is redundant
- Prefer when(x){is A...} for multi-branch
- Add ?.let when reusing the cast value
basics
~10 sWrite (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 sThe 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 linesfun lengthOrMinusOne(input: Any): Int =
(input as? String)?.length ?: -1
lengthOrMinusOne("abc") // 3
lengthOrMinusOne(3.14) // -1go deeper
Produces the (x as? String)?.length ?: -1 chain and knows each operator's role.
Explains the trade-off vs is/smart-cast and recognizes the is+as redundancy.
Chooses between expression chains and when based on number of branches and readability, avoiding exception-driven control flow.
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