skip to content

What subtle bug can the elvis-style assignment '= ' equals sign introduce, e.g. fun f() = someBuilder.add(x)? Discuss inferred Unit and the single-expression-vs-assignment confusion.

level: seniorimportance: should knowfreq 35%

answer

  1. Expression body infers Unit silently
  2. Declare return type to catch accidental Unit
  3. = { ... } is a lambda, not a block body
  4. Block body has braces with NO =
  5. TODO()/Nothing satisfies any declared type but throws

basics

~20 s

If the single expression returns nothing useful (Unit), the function silently returns Unit, which may not be what you intended. Also, beginners sometimes confuse the body equals sign with assigning a lambda, accidentally returning a function instead of calling it.

solid answer

~50 s

Two traps. First, **silent Unit inference**: `fun save(u: User) = repository.persist(u)` infers whatever `persist` returns; if that is `Unit`, the function returns `Unit` even if the author meant to return an id — there is no compiler warning because `Unit` is a valid value. Always declare the return type on functions that are supposed to produce a value. Second, **expression body vs. lambda**: `fun handler() = { doWork() }` does NOT call `doWork()`; it returns a `() -> Unit` lambda (inferred type `() -> Unit`), because `{ ... }` here is a lambda literal, not a block body. To run the work, drop the braces: `fun handler() = doWork()`. A related gotcha: `fun f() = { it }` is also a lambda. Reviewers should watch for an `=` followed by `{` and confirm whether a value or a function was intended.

code

kotlin · 8 lines
kotlin
fun run1() = { println("hi") }   // returns () -> Unit; nothing prints when called
fun run2() = println("hi")       // prints; returns Unit

fun main() {
    run1()       // prints nothing
    run1()()     // prints "hi"
    run2()       // prints "hi"
}

go deeper

for a junior

Can distinguish calling a function from returning a lambda after some thought.

for a middle

Knows = { } yields a lambda and that block body has no equals sign.

for a senior

Proactively declares return types to catch silent Unit and flags = { confusion in reviews.

for a principal

Establishes lint rules / explicit-api policy so these traps are caught mechanically, not by luck.

## Trap 1 — silent `Unit` inference An expression body infers its return type. If the expression evaluates to `Unit` (the type of "no meaningful value", whose sole instance is `Unit`), the function returns `Unit` quietly: ```kotlin // Intended to return the saved id, but persist() returns Unit fun save(u: User) = repository.persist(u) // inferred return type: Unit! ``` Nothing warns you, because `Unit` is a perfectly legal return type. The defense is to **declare the intended return type**; the compiler then errors if the body doesn't produce it: ```kotlin fun save(u: User): Long = repository.persist(u) // compile error if persist() is Unit ``` ## Trap 2 — expression body vs. lambda literal After `=`, a `{ ... }` is **not** a block body — block bodies attach directly to the function signature with no `=`. With `=`, braces are a **lambda literal**: ```kotlin fun handler() = { doWork() } // returns a () -> Unit lambda; doWork is NOT called here fun handler2() = doWork() // calls doWork(); returns whatever it returns ``` So `handler()` returns a function object; callers would have to invoke it (`handler()()`) to run `doWork`. This is an easy-to-miss bug, especially when refactoring a block body into an expression body and accidentally leaving the braces. ## The two body forms, side by side ```kotlin fun a(): Int { return 1 } // block body: braces, no =, explicit return fun b(): Int = 1 // expression body: =, the expression is the result fun c() = { 1 } // expression body returning a lambda () -> Int ``` ## Related: `Nothing` and `TODO()` `fun parse(): Int = TODO()` compiles because `TODO()` returns `Nothing`, a subtype of every type, so it satisfies the `Int` contract while throwing at runtime — useful scaffolding but a runtime trap if shipped. ## Review heuristics - An `=` immediately followed by `{` deserves a second look: value or function? - Functions meant to produce a value should declare their return type so accidental `Unit` is caught. - Prefer block body when you actually want a multi-statement body — don't reach for a lambda.

  • How do you defend against an expression-body function accidentally returning Unit?
    Declare the intended non-Unit return type explicitly; the compiler then errors if the body's expression evaluates to Unit instead of the expected type.
  • Why does 'fun f() = { x }' return a lambda rather than x?
    With =, the braces are a lambda literal, so the function's single expression is the lambda object () -> typeof(x), not the evaluation of x.

saying these in an interview costs you the question

  • Assuming = { ... } is just a block body and the code inside runs immediately
  • Never declaring return types, masking accidental Unit returns
  • Thinking the compiler warns when a value-returning function silently yields Unit
  • Not recognizing that block body uses braces WITHOUT an equals sign
  • Believing TODO() makes the function safe rather than throwing at runtime

context