The Elvis Operator (?:)

The Elvis operator, written ?:, is Kotlin’s shorthand for “use this value, or fall back to that one if it’s null.” Instead of writing an if statement every time a nullable value might be missing, you write one short expression that supplies a default. It gets its playful name from the resemblance between ?: and Elvis Presley’s hairstyle-and-eye emoticon. Because Kotlin’s entire null-safety system revolves around distinguishing nullable types from non-null types, the Elvis operator ends up being one of the most frequently used operators in idiomatic Kotlin code.

Overview / How it works

Kotlin splits every type into a nullable version and a non-null version: a variable of type String can never hold null, while a variable of type String? can. The compiler enforces this at compile time — if you try to use a String? anywhere a plain String is required (calling a member function on it directly, passing it to a function expecting a non-null parameter, and so on) without first proving it isn’t null, the code simply will not compile. The Elvis operator is one of the tools Kotlin gives you to satisfy that proof.

The expression a ?: b evaluates a first. If a is not null, its value becomes the value of the whole expression and b is never touched. If a is null, then b is evaluated and becomes the value of the expression instead. This is exactly the same logic as Java’s a != null ? a : b ternary, but far more compact — and unlike the naive ternary, Kotlin only evaluates a once, which matters when a is itself a function call or has side effects.

Type inference is where the Elvis operator earns its keep. If the left-hand side has type T? and the right-hand side has a non-null type T (or a supertype of it), the compiler infers the type of the whole a ?: b expression as non-null. That’s because logically, whichever branch actually runs, the result is guaranteed not to be null: either a was non-null, or b ran and b is non-null by its own type. This lets you take a nullable value and, in one line, produce a non-null value the rest of your code can use freely without further null checks.

The right-hand side isn’t limited to plain values. It can be any expression, including throw or return (and, inside loops, break or continue). These control-flow keywords have the special Kotlin type Nothing, which is defined as a subtype of every other type. That’s what makes val x = maybeNull ?: throw IllegalStateException("missing") type-check: the compiler is happy to unify a String? on the left with a Nothing-typed throw on the right, and the result is a non-null String. This pattern — often called a guard clause — is the idiomatic Kotlin replacement for the defensive “if the argument is null, bail out” boilerplate you’d write at the top of a Java method.

Syntax

expression1 ?: expression2
Part Meaning
expression1 Any expression with a nullable type. Evaluated exactly once.
?: The Elvis operator itself — an infix operator, not a method call.
expression2 The fallback. Only evaluated if expression1 is null. Can be a value, a function call, or a throw/return.

The operator is right-associative, so it chains naturally for a list of prioritized fallbacks:

primary ?: secondary ?: "final default"

This tries primary first, falls back to secondary if that’s null, and only reaches the string literal if both are null.

Examples

The first example shows the basic substitution pattern: pick the real value if present, otherwise fall back to a constant.

fun main() {
    val name: String? = null
    val displayName = name ?: "Guest"
    println(displayName)

    val name2: String? = "Alice"
    val displayName2 = name2 ?: "Guest"
    println(displayName2)
}

Output:

Guest
Alice

When name is null, the Elvis operator supplies "Guest". When name2 already holds a value, that value passes straight through and "Guest" is never evaluated.

The second example combines the Elvis operator with the safe-call operator ?., which is the single most common pairing in real Kotlin code: reach into a nullable value safely, then supply a default if the whole chain came back null.

fun main() {
    val names: List<String?> = listOf("Alice", null, "Bob")
    for (name in names) {
        val length = name?.length ?: -1
        println("$name -> $length")
    }
}

Output:

Alice -> 5
null -> -1
Bob -> 3

For each nullable string, name?.length yields an Int?: the actual length if name isn’t null, or null itself if it is (the safe call short-circuits to null instead of throwing). The Elvis operator then converts that Int? into a plain Int, substituting -1 as a sentinel value wherever the name was missing.

The third example shows a more realistic use: validating an argument and converting a nullable, unparsed input into a clean, non-null result, using throw as the fallback branch.

fun parseAge(input: String?): Int {
    val trimmed = input?.trim() ?: throw IllegalArgumentException("Age input cannot be null")
    return trimmed.toIntOrNull() ?: -1
}

fun main() {
    println(parseAge("  25  "))
    println(parseAge("abc"))
    try {
        parseAge(null)
    } catch (e: IllegalArgumentException) {
        println("Caught: ${e.message}")
    }
}

Output:

25
-1
Caught: Age input cannot be null

Here two different Elvis operators do two different jobs. The first turns a missing (null) input into a thrown exception — a genuine error case. The second turns an unparseable string (toIntOrNull() returns null for non-numeric text) into a sentinel value -1 instead — a recoverable case with a sensible default. Choosing between “throw” and “default” on the right-hand side is a design decision, not a syntax rule.

How it works step by step

Evaluation of a ?: b always proceeds in this order:

  1. Evaluate a exactly once, storing its result.
  2. Check whether that result is null.
  3. If it is not null, that value becomes the result of the whole expression — b is never evaluated at all.
  4. If it is null, evaluate b and use its value as the result.

The fact that b is only evaluated when needed (lazy, short-circuit evaluation) matters whenever the fallback is expensive or has side effects, such as a function call, a database lookup, or a log statement.

fun computeDefault(): Int {
    println("Computing expensive default...")
    return 0
}

fun main() {
    val a: Int? = 5
    val result = a ?: computeDefault()
    println("Result: $result")

    val b: Int? = null
    val result2 = b ?: computeDefault()
    println("Result: $result2")
}

Output:

Result: 5
Computing expensive default...
Result: 0

Notice that "Computing expensive default..." is printed only once, for the second call. When a already had the value 5, computeDefault() was never invoked — its println never ran. If you’d written this with a naive two-branch check that called computeDefault() to both test and use the value, you would evaluate it twice; the Elvis operator’s single evaluation of the left side avoids that entirely.

Common Mistakes

Mistake 1: reaching for !! instead of a real default

The non-null assertion operator !! forces a nullable value to be treated as non-null, and throws a NullPointerException at runtime if it actually is null. It’s tempting to sprinkle !! everywhere just to silence the compiler, but that throws away the entire benefit of Kotlin’s null-safety system — you’re trading a compile-time guarantee for a runtime crash.

fun main() {
    val input: String? = null
    val length = input!!.length
    println(length)
}

Output:

Throws a NullPointerException at runtime (input!! fails because input is null); no output is printed before the crash.

Almost every use of !! can be replaced by an Elvis operator with a sensible fallback, which keeps the program running instead of crashing:

fun main() {
    val input: String? = null
    val length = input?.length ?: 0
    println(length)
}

Output:

0

Mistake 2: forgetting that . binds tighter than ?:

Member access (.) has higher precedence than the Elvis operator. That means a ?: b.c is parsed as a ?: (b.c), not (a ?: b).c. It’s easy to write code intending the second meaning and silently get the first.

fun main() {
    val name: String? = "hello"
    val greeting = name ?: "guest".uppercase()
    println(greeting)
}

Output:

hello

The author likely wanted “use the name if present, otherwise ‘guest’, and uppercase whichever one wins” — but .uppercase() binds only to the literal "guest", so when name is non-null it passes through completely untouched. Parenthesizing the Elvis expression fixes it:

fun main() {
    val name: String? = "hello"
    val greeting = (name ?: "guest").uppercase()
    println(greeting)
}

Output:

HELLO

Whenever an operation should apply to the result of an Elvis expression rather than just its right-hand side, wrap the whole a ?: b in parentheses first.

Best Practices

  • Prefer ?: over !! whenever there’s a reasonable fallback value — reserve !! for cases you can prove are truly impossible to be null.
  • Pair it with the safe-call operator for nested nullable chains: user?.address?.city ?: "Unknown" is idiomatic and reads naturally left to right.
  • Use ?: return, ?: continue, or ?: throw as guard clauses at the top of a function to exit early on missing data, instead of nesting the rest of the function inside an if (x != null) block.
  • Parenthesize the left side, (a ?: b).c, whenever a following member access or operator should apply to the whole Elvis result, not just to b.
  • Don’t use a default value to silently swallow a real bug; if a null genuinely signals an error condition, throwing is often more honest than defaulting.
  • Chain ?: for prioritized fallbacks (primary ?: secondary ?: fallback) instead of nesting multiple if statements.
  • Keep the right-hand side focused on producing a default value — if you need real branching logic with multiple effects, an explicit if or ?.let { } is clearer.

Practice Exercises

  • Write a function greetUser(name: String?): String that returns "Hello, " followed by the name if it’s present, or "Hello, stranger!" if it’s null — using only the Elvis operator, no if/else. Calling it with null should print Hello, stranger!.
  • Given val scores: List<Int>? = null, write a one-line expression using ?: that produces a non-null list (falling back to emptyList()), then print its size. Expected output: 0.
  • Write a function safeDivide(a: Int, b: Int?): Int that returns a divided by b, or -1 if b is either null or 0. Hint: you’ll need to combine a null check and a zero check before your ?: fallback applies cleanly.

Summary

  • ?: (the Elvis operator) returns its left operand if that operand is not null, otherwise it evaluates and returns the right operand.
  • It short-circuits: the right-hand expression only runs when the left side turns out to be null, and the left side is only ever evaluated once.
  • When the right-hand side has a non-null type, the compiler infers the whole a ?: b expression as non-null, eliminating the need for further null checks downstream.
  • The right side can be throw or return because they have the special type Nothing, a subtype of every type — this powers the common early-exit guard-clause pattern.
  • Member access . binds tighter than ?:, so wrap the Elvis expression in parentheses, (a ?: b).c, whenever an operation should apply to its result.
  • Prefer ?: with a meaningful fallback over !!, which trades a compile-time safety guarantee for a runtime crash.