skip to content

When can you drop the empty `()` entirely, as in `repeat(3) { }` vs `run { }`? Explain the rule precisely.

level: middleimportance: must knowfreq 65%

answer

  1. Drop () only when it would be empty
  2. run{} = sole param; repeat(3){} = extra arg
  3. repeat {} alone is a compile error
  4. Defaults let you still drop ()
  5. Move-out and drop-parens are two separate steps

basics

~20 s

You drop the parentheses only when the lambda is the function's single argument and nothing else is left inside them. run takes only a lambda, so run { }. repeat(3) { } still needs (3) because 3 is a separate argument.

solid answer

~40 s

After moving the trailing lambda outside the parentheses, you may delete the parentheses **only if they would be empty** — i.e. the lambda was the call's *only* argument. `run { }`, `apply { }`, and `synchronized(lock) { }`’s lambda are examples: `run` has no other parameters, so `()` vanishes entirely. With `repeat(3) { }` the count `3` remains inside the parentheses, so they stay. The same applies to `list.fold(0) { acc, x -> acc + x }` — the seed `0` keeps the parentheses. So the rule is two-step: (1) any final-lambda argument may move out; (2) the parentheses may be removed only when they end up empty. Writing `run() { }` is legal but non-idiomatic; `repeat { }` without `(3)` is a compile error because `count` is required.

code

kotlin · 9 lines
kotlin
// sole lambda parameter -> () gone
val r = run { "hello" }

// extra required arg -> () stays
repeat(2) { println("hi") }

// every non-lambda param has a default -> () can be dropped
fun retry(times: Int = 3, body: () -> Unit) { repeat(times) { body() } }
retry { println("go") }      // times defaults to 3

go deeper

for a junior

Knows run { } drops parens but repeat(3) { } keeps them, even if can't state the rule precisely.

for a middle

States the two-step rule: move out, then drop parens only if empty; predicts repeat { } is an error.

for a senior

Adds the default-argument nuance and explains why the IDE flags run() { }.

for a principal

Connects this to designing function signatures whose call sites collapse to a clean block.

## Two independent rules The trailing-lambda convention is really two rules applied in sequence: 1. **Move out:** if the last parameter is a function type, its lambda may be written after the parentheses. 2. **Drop empty parens:** if, after moving the lambda out, the parentheses contain no arguments, you may delete them. ## When parens disappear They disappear only for functions whose **sole** parameter is the lambda: ```kotlin inline fun <R> run(block: () -> R): R = block() val x = run { 42 } // () dropped — only parameter was the lambda val y = run() { 42 } // legal but redundant; IDE will flag it ``` Standard-library scope functions `run`, `apply`, `also`, `let`, `with` (well, `with(x) { }` keeps the receiver arg), and `runCatching` follow this. ## When parens stay If any non-lambda argument remains, the parentheses are mandatory: ```kotlin repeat(3) { i -> println(i) } // 3 stays inside buildList(16) { add(1) } // initial capacity stays list.fold("") { acc, s -> acc + s } // seed stays ``` `repeat { }` is a **compile error** — `repeat(times: Int, action: ...)` requires `times`. ## Default arguments edge case If every non-lambda parameter has a default, you can still drop the parens: ```kotlin fun greet(name: String = "world", block: (String) -> Unit) = block(name) greet { println(it) } // name defaults to "world", () dropped greet("Kotlin") { println(it) } ``` So the precise rule is: parentheses may be dropped when, after moving the trailing lambda out, **no argument needs to be supplied** inside them (all remaining parameters are absent because they have defaults or there are none).

  • Why does `repeat { println("x") }` fail to compile?
    `repeat` is declared `repeat(times: Int, action: (Int) -> Unit)`. `times` has no default, so dropping `(3)` leaves a required argument unsupplied.
  • Is `run() { 1 }` wrong?
    It compiles and behaves identically to `run { 1 }`, but the empty `()` is redundant and IDE inspections flag it as non-idiomatic.

saying these in an interview costs you the question

  • Claims you can always omit () when using a trailing lambda
  • Thinks repeat { } compiles
  • Cannot explain the default-argument case
  • Believes dropping () changes which overload is selected in a meaningful runtime way

context