skip to content

How do you declare a class or interface with multiple type parameters, like Map<K, V>, and reference those parameters across the members?

level: middleimportance: must knowfreq 60%

answer

  1. Comma-separate params: <K, V>
  2. Positional matching — no named type args
  3. Each param is independent; can have its own variance
  4. Map<K, out V>: K invariant, V covariant
  5. All uses of K share one concrete type per instance

basics

~10 s

List the parameters comma-separated in angle brackets: class Pair<K, V>(val key: K, val value: V). Each name is its own placeholder, and you can use both anywhere a type is needed inside the class.

solid answer

~40 s

Separate multiple type parameters with commas inside the angle brackets: `class Entry<K, V>(val key: K, val value: V)` or `interface Map<K, out V>`. Each parameter is an independent placeholder filled by a distinct type argument at the use site (`Map<String, Int>`). You reference any of them across properties, function parameters, and return types — e.g. `fun put(key: K, value: V): V?`. The order at declaration and use must match positionally; there are no named type arguments. Conventional names communicate intent: `K`/`V` for key/value, `T`/`R` for input/result. You can mix declaration-site variance per parameter independently, as the standard library's `Map<K, out V>` shows: `K` is invariant, `V` is covariant. All uses of a given parameter within one instance resolve to the same concrete type.

code

kotlin · 9 lines
kotlin
class Entry<K, V>(val key: K, val value: V) {
    fun withValue(newValue: V): Entry<K, V> = Entry(key, newValue)
}

fun main() {
    val e = Entry("id", 42)          // Entry<String, Int>
    val e2 = e.withValue(99)
    println("${e2.key} -> ${e2.value}")
}

go deeper

for a junior

Knows you comma-separate multiple parameters like <K, V> and supply two type arguments.

for a middle

Explains positional (not named) matching and that each parameter is independent across members.

for a senior

Notes per-parameter variance (Map<K, out V>) and why K stays invariant while V is covariant.

for a principal

Discusses API ergonomics of multi-parameter generics and when fewer parameters or a data class improves the contract.

## Declaring several type parameters A generic type can take **more than one** type parameter. List them comma-separated inside the angle brackets after the class or interface name: ```kotlin class Entry<K, V>(val key: K, val value: V) { fun keyMatches(other: K): Boolean = key == other fun withValue(newValue: V): Entry<K, V> = Entry(key, newValue) } ``` Each parameter (`K`, `V`) is an **independent placeholder**. The caller supplies a distinct type argument for each, positionally: ```kotlin val e = Entry("id", 42) // Entry<String, Int> val f: Entry<Int, Boolean> = Entry(1, true) ``` ## Positional, not named Type arguments are matched **by position**, in the same order as the declaration. Kotlin has **no named type arguments** — `Entry<V = Int, K = String>` is not valid. Order matters: `Map<String, Int>` differs from `Map<Int, String>`. ## Referencing parameters across members Any declared parameter may appear anywhere a type is expected inside the body: property types, function parameter types, return types, even nested generics like `List<V>` or `Entry<K, V>`. Within a single instance, every occurrence of `K` resolves to the **same** concrete type, and likewise for `V`. ## The standard library example: Map `kotlin.collections.Map` is declared roughly as: ```kotlin interface Map<K, out V> { val keys: Set<K> val values: Collection<V> operator fun get(key: K): V? } ``` Note each parameter can carry its **own variance**: `K` is **invariant** (it appears in the `in` position of `get`), while `V` is marked `out` (**covariant**) because it only flows outward. This is independent per parameter — a single declaration can combine invariant and variant parameters. ## Naming conventions - `K`, `V` — key, value - `T`, `R` — input type, result type - `E` — element Use them to make intent obvious; the compiler treats any identifier the same.

  • Can you supply type arguments by name, e.g. Entry<V = Int>?
    No. Kotlin matches type arguments strictly by position; there is no named-type-argument syntax.
  • Can different type parameters in the same class have different variance?
    Yes. Variance is declared per parameter, e.g. Map<K, out V> makes K invariant and V covariant independently.

saying these in an interview costs you the question

  • Believing type arguments can be passed by name
  • Thinking all type parameters must share the same variance
  • Confusing the declaration order so Map<String, Int> and Map<Int, String> are treated as equal
  • Saying you can only have one type parameter per class

context