The Elvis operator, let, and smart casts
The safe call gives you null when something is absent. But often you do not want null — you want a
fallback, a sensible default, or to run a block only when the value is present. This lesson
covers the two tools for that: the Elvis operator ?: and the let function, plus how they
combine into some of the most idiomatic lines in Kotlin.
The Elvis operator ?:
The Elvis operator provides a default when the left side is null:
val name: String? = null
val display = name ?: "Guest"
println(display) // Guest
val realName: String? = "Kavita"
println(realName ?: "Guest") // Kavita
Read a ?: b as "a, or if that is null, b". It is the clean answer to "use this value, but
fall back to something if it is missing". (The name is a joke — turned sideways, ?: looks like
Elvis Presley's hair.)
It pairs perfectly with the safe call to turn a nullable chain into a guaranteed value:
val city: String? = customer?.address?.city
val label = customer?.address?.city ?: "Unknown city"
println(label) // "Unknown city" if any link was null
customer?.address?.city ?: "Unknown city" is a complete, idiomatic line: walk the nullable chain,
and if anything along it is null, use the default. The result is a plain String — non-null,
because the Elvis guaranteed it. This is how you get from String? to String: give a fallback.
Elvis with early return or throw
The right side of ?: can be any expression — including return or throw, which is enormously
useful for the "handle absence at the top" pattern:
fun greet(name: String?) {
val realName = name ?: return // if null, leave the function now
println("Namaste, $realName") // realName is String from here on
}
fun requireCity(customer: Customer?): String {
return customer?.address?.city
?: throw IllegalArgumentException("Customer has no city")
}
val realName = name ?: return reads as "get the name, or give up". After that line, realName is
a guaranteed non-null String, so the rest of the function is clean. This is the Elvis version of
the early-return smart-cast pattern from the last lesson, and it is one of the most common shapes in
real Kotlin: use the value, or bail out — in one line.
The let function
let runs a block with the value as its argument, and returns the block's result. Its most valuable
use is with ?., to run code only when a value is non-null:
val name: String? = "Kavita"
name?.let {
println("Name is $it") // runs only if name is non-null
println("It has ${it.length} letters")
}
val missing: String? = null
missing?.let {
println("This never runs") // skipped entirely — missing is null
}
Inside the let block, it is the non-null value (you can also name it: name?.let { n -> ... }).
The ?.let { } combination means "if this is present, do this with it" — a very common need, and
much cleaner than a nested if (x != null) { ... } when you have a short block to run.
let also shines for transforming a nullable into something else with a fallback:
val input: String? = "42"
val number = input?.let { it.toIntOrNull() } ?: 0
println(number) // 42, or 0 if input was null or not a number
Combining them — the idiomatic lines
Elvis and let together produce the lines that mark someone who knows Kotlin. Read these slowly;
each is a pattern you will reuse constantly:
// "use the value if present, else a default"
val display = username ?: "Guest"
// "transform if present, else a default"
val length = username?.length ?: 0
// "do something if present" (no else needed)
username?.let { saveToDatabase(it) }
// "get it or give up"
val user = findUser(id) ?: return
// "get it or fail loudly"
val user = findUser(id) ?: error("No user with id $id")
Notice what these have in common: null is handled inline, at the point of use, in one readable
expression — no separate if block, no temporary variable, no crash. That density-with-clarity is
the payoff of Kotlin's null handling, and it is why nullable types feel like a help rather than a
tax once you have the operators.
A word on overusing let
let is lovely, and beginners who discover it start putting it everywhere. Resist. A plain
if (x != null) with a smart cast is often clearer than x?.let { }, especially for a longer
block or when you also need an else branch. Reach for ?.let { } when you have a short action to
run only on the non-null case; reach for if/Elvis otherwise. The idiomatic-Kotlin lesson in
the best-practices module returns to this — the scope functions (let, run, also, apply,
with) are powerful and easy to overuse into unreadable code.
Check your work
What a ?: b means. a, or if a is null, b — a default for a nullable value.
How to turn a String? into a String. Give a fallback with ?: (or handle null another way).
What name ?: return does. Leaves the function if name is null; otherwise binds a guaranteed
non-null value.
What can go on the right of ?:. Any expression, including return, throw, and error(...).
What ?.let { } does. Runs the block only if the value is non-null, with it bound to the
non-null value.
How to transform a nullable with a fallback. x?.let { transform(it) } ?: default.
The common thread of the idiomatic lines. Null handled inline at the point of use, in one readable expression.
When to prefer if/smart-cast over ?.let. For a longer block, or when you need an else — a
plain if (x != null) is clearer.
Practice
- Use
?:to give a nullable name a default of"Guest". Test with a value and with null. - Write
val n = input?.length ?: 0and test with a string and with null. - Write a function that does
val name = param ?: returnand prints only when a name was given. - Write one that does
?: throw IllegalArgumentException(...)and observe the exception when the value is null. - Use
name?.let { }to print two lines about the name, and confirm the block is skipped when the name is null. - Chain it:
input?.let { it.toIntOrNull() } ?: -1, and test with"42","abc", and null. - Take a nested
if (x != null) { println(x) }and rewrite it asx?.let { println(it) }. Then decide, for a five-line block, which you would actually prefer.
Official documentation
- Kotlin — Null safety: Elvis operator —
?:in detail. - Kotlin — Scope functions: let — What
letdoes and when to use it. - Kotlin — Scope functions overview — All five, and how to choose (the best-practices module goes deeper).
Next: !!, and why it is almost always the wrong answer.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship