Kotlin Generics

Type-safe reusable code with generic classes, functions, constraints, variance, and reified types

<T> out in reified

Table of Contents

1Generic Classes

Generic classes declare one or more type parameters (e.g. T) so the class can work with any type while keeping compile-time type safety — avoiding casts and runtime errors.

Generic class Boxclass Box<T>(var value: T) { fun get(): T = value fun set(item: T) { value = item } } fun main() { val intBox = Box(42) // Box<Int> — type inferred val stringBox = Box("Kotlin") // Box<String> println(intBox.get()) // 42 println(stringBox.get()) // Kotlin }

Multiple type parameters

Pair-like generic classclass PairHolder<A, B>(val first: A, val second: B) { fun swap(): PairHolder<B, A> { return PairHolder(second, first) } } fun main() { val pair = PairHolder("Name", 100) val swapped = pair.swap() println("${swapped.first}, ${swapped.second}") // 100, Name }

Why generics matter

Without genericsWith generics
Box stores Any? — need castsBox<T> — type known at compile time
Runtime ClassCastException riskCompiler catches type mismatches
Less readable APISelf-documenting: List<User>
Standard library examplesval names: List<String> = listOf("Kotlin", "Java") val cache: MutableMap<String, Int> = mutableMapOf() val ids: Result<Int> = Result.success(42)
Type erasure

On the JVM, generic type parameters are erased at runtime — List<String> becomes List. Use reified types with inline functions when you need runtime type access.

2Generic Functions

A generic function has its own type parameters independent of the enclosing class. Declare them before the function name with angle brackets.

Standalone generic functionfun <T> singletonList(item: T): List<T> { return listOf(item) } fun <T> T.applyTwice(block: (T) -> T): T { return block(block(this)) } fun main() { println(singletonList("Hello")) // [Hello] println(5.applyTwice { it * 2 }) // 20 }

Generic functions inside classes

Class with generic methodclass Utils { fun <T> printAll(items: List<T>) { items.forEach { println(it) } } fun <T> firstOrNull(items: List<T>): T? { return items.firstOrNull() } } fun main() { Utils().printAll(listOf(1, 2, 3)) println(Utils().firstOrNull(listOf("a", "b"))) // a }

Extension generic functions

Generic extensionfun <T> List<T>.secondOrNull(): T? { return if (size >= 2) this[1] else null } fun main() { println(listOf(10, 20, 30).secondOrNull()) // 20 println(listOf(10).secondOrNull()) // null }
SyntaxExample
Function type paramfun <T> identity(x: T): T = x
Multiple paramsfun <K, V> mapOf(): Map<K, V>
Extensionfun <T> T.run(block: (T) -> R): R
Stdlib: Collection functions like map, filter, and fold are generic — see Collections and Lambda & Higher-Order Functions.

3Type Constraints

Type constraints limit which types can be used as type arguments — so you can safely call methods on T that belong to a specific superclass or interface.

Upper bound with :

T must extend Numberfun <T : Number> sum(a: T, b: T): Double { return a.toDouble() + b.toDouble() } fun main() { println(sum(10, 20)) // 30.0 println(sum(3.5, 2.5)) // 6.0 // sum("a", "b") // Compile error — String is not a Number }

Multiple constraints with where

where clausefun <T> max(a: T, b: T): T where T : Comparable<T> { return if (a >= b) a else b } fun main() { println(max(10, 25)) // 25 println(max("apple", "banana")) // banana }
Class with where constraintsclass SortedList<T> : Iterable<T> where T : Comparable<T> { private val items = mutableListOf<T>() fun add(item: T) { items.add(item) items.sort() } override fun iterator(): Iterator<T> = items.iterator() }
Constraint syntaxMeaning
<T : Number>T must be Number or subclass
<T : Comparable<T>>T must be comparable to itself
where T : A, T : BT must implement both A and B
Default upper boundAny? if none specified
Nullable type param

By default T can be nullable. Use T : Any to require a non-null type argument.

4Variance

Variance describes how subtyping between generic types relates to subtyping of their type arguments. Kotlin uses declaration-site variance with out and in keywords.

Covariance — out (producer)

out T means T appears only in out (return) positions — the type is a producer of T. If Dog is a subtype of Animal, then List<Dog> can be treated as List<out Animal>.

Covariant interfaceinterface Producer<out T> { fun produce(): T } class DogProducer : Producer<Dog> { override fun produce() = Dog("Buddy") } open class Animal(val name: String) class Dog(name: String) : Animal(name) fun feedAll(producer: Producer<Animal>) { val animal: Animal = producer.produce() println("Feeding ${animal.name}") } fun main() { feedAll(DogProducer()) // OK — Producer<Dog> is subtype of Producer<Animal> }

Contravariance — in (consumer)

in T means T appears only in in (parameter) positions — the type is a consumer of T.

Contravariant interfaceinterface Consumer<in T> { fun consume(item: T) } class AnimalConsumer : Consumer<Animal> { override fun consume(item: Animal) { println("Consumed ${item.name}") } } fun feedDog(consumer: Consumer<Dog>) { consumer.consume(Dog("Max")) } fun main() { feedDog(AnimalConsumer()) // OK — Consumer<Animal> is subtype of Consumer<Dog> }

Variance summary

KeywordNameT used asSubtyping
out TCovariantOutput (return) onlyPreserves subtyping direction
in TContravariantInput (parameter) onlyReverses subtyping direction
T (none)InvariantBoth in and outNo subtyping — exact match
List is covariant (read-only)// List<out E> in Kotlin — you can assign: val dogs: List<Dog> = listOf(Dog("Rex")) val animals: List<Animal> = dogs // OK for read-only List // MutableList is invariant — cannot assign MutableList<Dog> to MutableList<Animal>
PECS mnemonic: Producer Extends → out; Consumer Super → in. Kotlin builds this into declarations instead of use-site wildcards like Java's ? extends / ? super.

5Reified Types

Normally JVM type erasure removes generic type info at runtime. Kotlin's reified type parameters — used with inline functions — preserve the actual type at runtime so you can use T::class, is T, and reflection without passing Class<T> manually.

reified type parameterinline fun <reified T> isInstance(value: Any): Boolean { return value is T } inline fun <reified T> List<*>.filterIsInstance(): List<T> { return filterIsInstance<T>() } fun main() { println(isInstance<String>("hello")) // true println(isInstance<Int>("hello")) // false val mixed = listOf(1, "two", 3, "four") val strings = mixed.filterIsInstance<String>() println(strings) // [two, four] }

Requirements for reified

RequirementReason
Must be inline funCompiler copies bytecode per actual type
reified keyword on type paramMarks T as preserved at call site
Cannot use on non-inline functionsErasure applies at runtime otherwise
Cannot use on class type paramsOnly inline function type parameters

Practical reified examples

startActivity pattern (Android-style)inline fun <reified T> createInstance(): T { return T::class.java.getDeclaredConstructor().newInstance() } // Simplified intent-style navigation concept: inline fun <reified T> navigateTo(): String { return "Opening ${T::class.simpleName}" } fun main() { println(navigateTo<String>()) // Opening String }
JSON / Gson-style helper conceptinline fun <reified T> parseJson(json: String, parser: (String, Class<T>) -> T): T { return parser(json, T::class.java) } // Usage: parseJson<User>(jsonString, gson::fromJson)

Without reified (Java-style workaround)

Pass Class<T> explicitlyfun <T> isInstance(value: Any, clazz: Class<T>): Boolean { return clazz.isInstance(value) } fun main() { println(isInstance("hello", String::class.java)) // true }
Stdlib uses reified

filterIsInstance<T>(), Retrofit's inline reified APIs, and many DI/navigation helpers rely on reified + inline. See Inline Functions.

6Summary Cheatsheet

TopicKey Takeaway
Generic Classesclass Box<T> — type-safe reusable containers
Generic Functionsfun <T> name(...) — independent type params
Type ConstraintsT : Bound, where T : A, T : B
Varianceout = producer/covariant; in = consumer/contravariant
Reified Typesinline fun <reified T> — runtime type access on JVM
JVM noteType erasure by default; reified needs inline
Next lessonException Handling

Kotlin Generics MCQ Practice

10 Basic MCQs 10 Advanced MCQs

10 Basic Kotlin Generics MCQs

1

Generic class syntax?

Aclass Box
Bgeneric class Box
CBox class
DT class Box
Explanation: See Kotlin Generics lesson — correct answer is A.
2

On JVM, generic types are?

AFully retained at runtime
BErased — type params removed at runtime
CStored in manifest
DNever compiled
Explanation: See Kotlin Generics lesson — correct answer is A.
3

out variance means?

AConsumer — can write T
BProducer — can read T, restricted write
CInvariant
DRaw type
Explanation: See Kotlin Generics lesson — correct answer is B.
4

in variance means?

AProducer out
BConsumer in — can write T, read restricted
CSame as out
DNo restrictions
Explanation: See Kotlin Generics lesson — correct answer is A.
5

reified type parameter requires?

Aabstract class
Binline function
CJava only
Denum
Explanation: See Kotlin Generics lesson — correct answer is B.
6

Star projection List<*> means?

AExact List
BUnknown type argument — read restrictions apply
CMutable any
DNull list
Explanation: See Kotlin Generics lesson — correct answer is A.
7

Upper bound syntax?

AT : Comparable
BT extends Comparable
CT implements Comparable
Dbound T
Explanation: See Kotlin Generics lesson — correct answer is A.
8

Default upper bound for type param?

AAny?
BAny (nullable) or non-null Any in practice
CObject only Java
DNothing
Explanation: See Kotlin Generics lesson — correct answer is A.
9

List is subtype of List?

AYes always
BNo — invariant; List is read-only but not safe for mutation
COnly in Java
DOnly for primitives
Explanation: See Kotlin Generics lesson — correct answer is B.
10

Declaration-site variance uses?

Aout/in on type declaration
BWildcards at use site only
CNo syntax
DJava extends only
Explanation: See Kotlin Generics lesson — correct answer is A.

10 Advanced Kotlin Generics MCQs

11

where clause?

AMultiple bounds T : A, B
BSQL only
CLoop only
DPackage name
Explanation: See Kotlin Generics lesson — correct answer is B.
12

inline fun enables?

Avalue is T at runtime
BOnly compile check
CRemove generics
DJava static
Explanation: See Kotlin Generics lesson — correct answer is A.
13

contravariant interface example?

AProducer out
BConsumer in — Comparable
CList out
DMap keys out
Explanation: See Kotlin Generics lesson — correct answer is B.
14

Type erasure workaround in Kotlin?

Areified with inline
BAlways reflection
CNo workaround
DUse Java only
Explanation: See Kotlin Generics lesson — correct answer is A.
15

Generic function declaration?

Afun foo(): T
Bfun foo() Java style only
CT fun foo()
Dgeneric fun
Explanation: See Kotlin Generics lesson — correct answer is B.
16

Covariant List accepts?

AList assignment to List
BMutable add to out list
CAny list writable
DPrimitive array always
Explanation: See Kotlin Generics lesson — correct answer is A.
17

invariant means?

ANo subtyping on type parameter
BAlways out
CAlways in
DStar only
Explanation: See Kotlin Generics lesson — correct answer is B.
18

fun interface generic?

AAllowed like normal interface
BNot allowed
COnly classes
DOnly objects
Explanation: See Kotlin Generics lesson — correct answer is A.
19

bounded type parameter T : Number allows?

AT operations as Number
BAny type
COnly Int
DNothing
Explanation: See Kotlin Generics lesson — correct answer is A.
20

Java wildcard ? extends Number maps to?

Ain Number
Bout Number in Kotlin producer
CStar projection only
DInvariant List
Explanation: See Kotlin Generics lesson — correct answer is A.
Click an option to select, then check answers.

Kotlin Generics Interview Q&A

15 topic-focused questions for interviews and revision.

1What are generics in Kotlin?easy
Answer: Type parameters for classes and functions: class Box(val value: T) — reuse code with type safety.
2What is type erasure on JVM?easy
Answer: Generic type info removed at runtime — List becomes List at bytecode; reified recovers type in inline functions.
3What does out variance mean?easy
Answer: Producer — interface only returns T (out position), not consumes as input — List can hold List.
4What does in variance mean?easy
Answer: Consumer — can accept T as input (in position) — Comparator contravariant.
5Difference between in and out?medium
Answer: out: read-only producer (covariant). in: write consumer (contravariant). Invariant: exact type match.
6What is reified?medium
Answer: inline fun — type T available at runtime for is checks and JavaClass — requires inline.
7Star projection List<*>?medium
Answer: List of unknown element type — read as Any?; cannot add typed elements safely.
8Upper bounds T : Comparable?medium
Answer: Restricts T to types implementing Comparable — can call compareTo inside generic code.
9Why List not subtype of List?medium
Answer: Invariant — if allowed, you could add Any to List reference pointing to List — type safety violation.
10where clause example?medium
Answer: fun copyWhenGreater(list: List, threshold: T) where T : Comparable — multiple bounds.
11Declaration-site vs use-site variance?hard
Answer: Kotlin uses declaration-site (out/in on interface). Java uses wildcards ? extends/? super at use site.
12When use reified filter?hard
Answer: inline fun List<*.filterIsInstance() — filter by runtime class despite erasure.
13Generic constraints on functions?hard
Answer: fun process(input: T) — T must be subtype of CharSequence.
14Covariant producer example?hard
Answer: interface Producer { fun produce(): T } — cannot accept T as method parameter in out position.
15How generics improve type safety?hard
Answer: Compile-time checks prevent ClassCastException — container knows element type without casting at every get().