This content originally appeared on DEV Community and was authored by Raji ac
In this blog post, we'll tackle some interesting coding questions related to Kotlin generics. But first, let's quickly recap the fundamentals.
Generics in Kotlin allow us to write classes, interfaces, and functions with a placeholder for a type, so as to create reusable and type-safe code that can work with different types.
Kotlin has declaration-site variance(with the generic class or interface's declaration) and use-site variance, aka type projections(in function signature).
out - keyword used to mention the type is a producer(can return values of type T).
in - used to mention the type is a consumer(can accept values of type T).
where - used to indicate that the type has more than one constraint.
reified - to preserve the type information at runtime, facilitates safe type-check with 'is' and 'as' at runtime.
** Any? is the super type of all types.
** Nothing is the ultimate subtype of all types.
interface Source<out T> {
fun nextT(): T
}
Covariance:- declared with the keyword 'out', denotes that the type parameter T is a covariant parameter or the class is covariant in the parameter 'T', or C can safely be a supertype of C, or as in Java - C<? extends Base>.
eg:- List
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
Contravariance:- In contrast to 'out', 'in' makes the type contravariant. C can be assigned to C
eg:-Comparable
Invariance:- Some classes accepts and produces types, they can be neither co- nor contravariant in T
eg:- Array
Type Erasure:- At runtime, the instance of the generic class does not hold any information about the type of parameter T, this is to ensure interoperability with old JVMs.
Star Projections:- Solves the problem of type safety when a generic type is unknown.
for interface Function<in T, out U>
Function<*, String>
means Function<in Nothing, String>
.
Function<Int, *>
means Function<Int, out Any?>
.
Function<*, *>
means Function<in Nothing, out Any?>
.
** To declare a generic type T as definitely non-nullable, declare the type with '& Any' (T & Any)
** Underscore(_) can be used for type arguments to automatically infer the type of the argument when other types are explicitly specified.
Now we know enough, let's dive in.
Q: Why isn't this allowed?
class Box<T>(val value: T)
fun takeBox(box: Box<Any>) { println(box.value) }
fun main() {
val strBox = Box("Hello")
takeBox(strBox)
}
Answer:
Generics are invariant by default. An Int
is a subtype of Any
, but a Box<Int>
is not a subtype of Box<Any>
.
Q: What would be the output?
fun <T> isString(value: T): Boolean {
return value is String
}
fun main() {
println(isString("Hello"))
println(isString(42))
}
Answer:
true
false
Even though the type information of T is erased to be available at runtime, value
is checked against the concrete type String
, thus is
results in true
for a String
and false
for an Int
Q: What does this mean?
fun <T> onlyNumbers(a: T, b: T): T
where T : Number, T : Comparable<T> {
return if (a > b) a else b
}
Answer:
Both parameters should be a Number
. where
is used to indicate multiple constraints, where T
is mentioned to be a subtype of Number
, also implement Comparable
interface and be able to compare itself to another instance of its own type, T
.
Q: Why wouldn't it compile?
fun copy(first: Array<out Any>, second: Array<Any>) {
for (i in first.indices) {
first[i] = second[i]
}
}
fun main() {
val strings = arrayOf("A", "B", "C")
val anys = Array<Any>(3) { "" }
copy(strings, anys)
}
Answer:
out
keyword on first
parameter type makes it a producer only; it cannot accept a value.
Q: Will this compile?
class Box<T>(val value: T) {
fun <T> printTwice(t: T) {
println("$value $t")
}
}
fun main() {
val box = Box("Kotlin")
box.printTwice(42)
}
Answer:
Yes. It will print Kotlin 42
, because the type parameter T of the class Box is shadowed in the function printTwice
, here it is considered as a new type.
Q: Will this code block compile?
fun fill(list: MutableList<out Number>) {
list.add(42)
}
Answer:
It will not, since the type parameter of the argument list
is denoted with the out
keyword, which means list
is a producer of Number
, and it can not accept.
Q: Why wouldn't this work?
fun mystery(list: MutableList<*>) {
list.add(null)
}
Answer:
MutableList<*>
is a star projection, which means MutableList<out Any?>
; it is a producer of Any?
Q: Why is this not allowed?
fun <T : Number> sum(a: T, b: T): T {
return a + b
}
Answer:
The operator function plus
is not defined in Number
, but in its subtypes such as Int
, Double
, etc. To make it work, these two Number
types will have to be converted to their subtypes.
Q: What will be the output?
inline fun <reified T> check(value: Any) {
println(value is T)
}
fun main() {
check<List<String>>(listOf("a", "b"))
check<List<Int>>(listOf("a", "b"))
}
Answer:
true
true
Even though reified
is mentioned, only the top-level type List
class is preserved; it can't overcome the JVM's fundamental type erasure for the arguments inside the angled brackets. In both cases, T
is only a List
type.
Q: Why is this not allowed?
object Cache<T> {
private val items = mutableListOf<T>()
}
Answer:
Because object
creates a singleton, it cannot have a different value, so generics are not allowed.
Q: Why do we need this?
interface ComparableSelf<T : ComparableSelf<T>> {
fun compareTo(other: T): Int
}
Answer:
This enforces the classes that implement this interface to have the method compareTo
, where it should accept its own type only; in short, it enforces a strict comparable.
Q: Is this a correct declaration?
class Box<T : Int>(val value: T)
Answer:
Int
is a final type, so the value of the type parameter is predetermined; it is enough to write class Box(val value: Int)
Q: What will be the output?
fun <T> printType(value: T) {
println("Generic")
}
fun printType(value: String) {
println("String")
}
fun main() {
printType("Hello")
}
Answer:
String
The Kotlin compiler selects the most specific function that matches the provided arguments.
Q: What will be the output?
inline fun <reified T> printClass(list: List<T>) {
println(T::class.java)
}
fun main() {
printClass(listOf("A", "B"))
printClass(listOf(1, 2, 3))
}
Answer:
class java.lang.String
class java.lang.Integer
reified
makes the type T
preserved.
This content originally appeared on DEV Community and was authored by Raji ac

Raji ac | Sciencx (2025-08-19T21:00:09+00:00) Kotlin:’Generics’ Questions. Retrieved from https://www.scien.cx/2025/08/19/kotlingenerics-questions/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.