skip to content

How does Kotlin infer the type of `it` (or named lambda parameters), and how can you make the parameter type explicit when needed?

level: seniorimportance: should knowfreq 40%

answer

  1. Param types inferred from expected function type
  2. it follows the same inference as named params
  3. No expected type -> must annotate (and no bare it)
  4. Annotate param `{ x: Int -> }` or val `val f: (Int)->Int`
  5. Explicit types help overloads/generics/readability

basics

~20 s

Kotlin figures out the type of it from the function you pass the lambda to (its expected type). If that's unclear, you can write the type yourself, like { it: Int -> it * 2 }.

solid answer

~50 s

A lambda's parameter types — including the implicit `it` — are **inferred from the expected type** (the function type of the parameter the lambda is passed to). In `list.map { it * 2 }`, `map` expects `(T) -> R`, so `it` is `T`. Because the type flows from context, you usually omit it. When there's **no expected type** (e.g. assigning a lambda to a `val` without an explicit function type), inference fails and you must annotate: `val f = { x: Int -> x * 2 }`, or give the val a type: `val f: (Int) -> Int = { it * 2 }`. You can also annotate inline for clarity: `{ it: Long -> ... }` or in destructuring `{ (a: Int, b) -> }`. Explicit types help with overload resolution, ambiguous generics, or readability. The implicit `it` follows the exact same inference rules as any named parameter.

code

kotlin · 11 lines
kotlin
// Inferred from map's (T) -> R
val doubled = listOf(1, 2, 3).map { it * 2 } // it: Int

// No expected type -> annotate the parameter
val square = { x: Int -> x * x }

// Or put the type on the variable; now bare it works
val inc: (Int) -> Int = { it + 1 }

// Explicit type to disambiguate / document
val parse: (String) -> Int = { it.trim().toInt() }

go deeper

for a junior

Knows it's type usually 'just works' from context like map.

for a middle

Explains inference from the expected function type and can annotate when assigning a standalone lambda.

for a senior

Knows the no-expected-type failure mode, that bare it is unavailable there, and uses explicit types for overload/generic disambiguation.

for a principal

Reasons about how lambda-typed public APIs and overload sets affect inference ergonomics and guides signature design to minimize annotation burden.

## Type inference for lambda parameters Kotlin determines lambda parameter types from the **expected type** — the **function type** of the slot the lambda fills. A function type looks like `(A, B) -> R`. When you pass a lambda there, Kotlin matches your parameters to `A`, `B` and the body's last expression to `R`. ```kotlin val nums: List<Int> = listOf(1, 2, 3) val doubled = nums.map { it * 2 } // map expects (Int) -> R, so it: Int ``` The implicit `it` is typed by **exactly the same rule** as a named parameter — it's just an auto-supplied name. ## When inference works vs. fails - **Works**: whenever there's an expected function type — passing to a function, or assigning to a val with an explicit function type. - **Fails**: when you write a standalone lambda with **no expected type**: ```kotlin val f = { x -> x * 2 } // ERROR: cannot infer type of x val g = { x: Int -> x * 2 } // OK: annotate the parameter val h: (Int) -> Int = { it * 2 } // OK: expected type given on the val ``` With a standalone lambda you also can't use bare `it` — there's no context to type it, so you must provide an explicit parameter list with types. ## How to make the type explicit 1. **Annotate the parameter**: `{ it: Long -> it + 1 }` (yes, you can even type the implicit name when you write it out). 2. **Type the variable/parameter**: `val h: (Int) -> Int = { it * 2 }`. 3. **In destructuring**: `{ (a: Int, b: String) -> }` — annotate components. ## Why annotate even when not required - **Overload resolution**: multiple overloads accept different function types; an explicit type disambiguates. - **Generic ambiguity**: helps the compiler pin a type parameter. - **Readability**: documents intent in complex generic call sites. ## Return type The **return type** is inferred from the last expression of the body; you generally don't annotate it on a lambda (that's where anonymous-function syntax would differ, but that's a separate feature). ## Summary `it` and named parameters are typed from the expected function type. Omit types when context provides them; annotate the parameter, the variable, or destructured components when there's no context, for overload/generic disambiguation, or for clarity.

  • Why does `val f = { x -> x * 2 }` fail to compile?
    There is no expected type to infer `x` from. Either annotate the parameter (`{ x: Int -> ... }`) or give `f` an explicit function type (`val f: (Int) -> Int = { it * 2 }`).
  • Can you annotate the type of the implicit `it`?
    Only by writing it out as a named parameter, e.g. `{ it: Long -> ... }`. Once you spell out a parameter list you can name it `it` and type it, but you can't annotate a bare implicit `it`.

saying these in an interview costs you the question

  • Thinking `it` has special typing rules different from named params
  • Believing `val f = { x -> x }` compiles without annotation
  • Not knowing the type comes from the expected function type
  • Claiming you can annotate a bare implicit `it`
  • Confusing lambda return-type inference with parameter inference

context