skip to content

When and how do you pass explicit type arguments to a generic function, e.g. maxOf<Int>(...)? Why might you do it even when inference would work?

level: middleimportance: should knowfreq 55%

answer

  1. Syntax: name<Type>(args) — after the name, before ()
  2. All-or-nothing for multiple params (no partial)
  3. Must use when T is return-only with no target
  4. Optional use: widen type, disambiguate, document
  5. Idiomatic Kotlin prefers inference; <...> is the exception

basics

~10 s

You write the type in angle brackets right after the function name, like maxOf<Int>(a, b). You do it when Kotlin can't figure the type out, or to force a specific type or improve clarity.

solid answer

~40 s

Explicit type arguments are written in angle brackets immediately after the function name and before the parentheses: maxOf<Int>(1, 2) or emptyList<String>(). You must supply them when inference is unconstrained — typically when the type parameter appears only in the return type with no expected target. You may also choose to supply them to override what inference would pick: maxOf<Any>(1, 2L) widens the result type, or to disambiguate overloads. They also document intent and prevent surprising least-upper-bound results. The arguments must satisfy any declared bounds. For a function with several parameters you provide them in order: pair<String, Int>("a", 1) — and it's all-or-nothing, you can't specify just one. Overuse is a smell: idiomatic Kotlin leans on inference, reserving explicit arguments for genuine ambiguity, return-only parameters, or deliberate widening.

code

kotlin · 7 lines
kotlin
val a = maxOf<Int>(1, 2)          // explicit, though inference would also give Int
val b = maxOf<Long>(1, 2)         // deliberate widening: result is Long
val e = emptyList<String>()       // required: return-only parameter

fun <K, V> entry(k: K, v: V) = k to v
val p = entry<String, Int>("a", 1) // both type args, in order
// val q = entry<String>("a", 1)   // ERROR: must give all or none

go deeper

for a junior

Knows the name<Type>(args) syntax and that it's used when Kotlin asks for the type.

for a middle

Explains both the required case (return-only parameter) and optional cases (widening, disambiguation), and the all-or-nothing rule.

for a senior

Judges when explicit arguments aid readability vs add noise, and uses them to break inference failures or shape public API result types.

for a principal

Sets team conventions on when explicit type arguments are warranted and designs signatures to minimize the need for them at call sites.

## Syntax Explicit **type arguments** go in angle brackets **after the function name** and before the call parentheses: ```kotlin maxOf<Int>(1, 2) emptyList<String>() listOf<Number>(1, 2L, 3.0) ``` For multiple type parameters, list them in declaration order; it is **all-or-nothing** — you cannot specify one and let the other be inferred: ```kotlin fun <K, V> entry(k: K, v: V): Pair<K, V> = k to v entry<String, Int>("a", 1) // both provided // entry<String>("a", 1) // ERROR: wrong number of type arguments ``` ## When you MUST provide them Inference has nothing to work from: ```kotlin fun <T> empty(): List<T> = emptyList() val x = empty<String>() // required: T only in return type, no target ``` ## When you CHOOSE to provide them (even though inference works) 1. **Widen the inferred type.** Inference picks the most specific type; you might want a broader one: ```kotlin val m = maxOf<Number>(1, 2L) // result typed Number instead of a narrower LUB val xs = listOf<Number>(1, 2) // List<Number>, not List<Int> ``` 2. **Disambiguate overloads** when several candidates match. 3. **Documentation / safety.** An explicit argument makes intent obvious and guards against an accidental least-upper-bound result (e.g. mixing types yielding `Any`). 4. **Break inference failures** in complex generic chains. ## Bounds still apply Explicit type arguments must satisfy the function's declared constraints; the compiler rejects arguments that violate an upper bound. ## Style Idiomatic Kotlin **prefers inference** — explicit type arguments are noise when the type is obvious. Reserve them for: return-only parameters, deliberate widening, disambiguation, or breaking an inference error. Reaching for `<...>` everywhere usually signals a misunderstanding of inference. ```kotlin // Idiomatic: let inference work val a = maxOf(1, 2) // Justified: deliberate widening val b = maxOf<Long>(1, 2) ```

  • Can you specify only some of a function's type arguments?
    No. Type arguments are all-or-nothing per call: you either provide every type parameter or let the compiler infer them all.
  • Give a reason to write explicit type arguments even when inference succeeds.
    To widen the result type (e.g. listOf<Number>(1, 2) instead of List<Int>), to disambiguate overloads, or to document intent and avoid an accidental Any from least-upper-bound.

saying these in an interview costs you the question

  • Putting the type args before the name (Kotlin uses name<T>(...))
  • Believing you can specify just one of several type parameters
  • Using explicit type arguments everywhere instead of relying on inference
  • Thinking explicit type arguments can bypass declared upper bounds
  • Not knowing widening (listOf<Number>) as a legitimate use

context