Kotlin:’Generics’ Questions

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…


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


Print Share Comment Cite Upload Translate Updates
APA

Raji ac | Sciencx (2025-08-19T21:00:09+00:00) Kotlin:’Generics’ Questions. Retrieved from https://www.scien.cx/2025/08/19/kotlingenerics-questions/

MLA
" » Kotlin:’Generics’ Questions." Raji ac | Sciencx - Tuesday August 19, 2025, https://www.scien.cx/2025/08/19/kotlingenerics-questions/
HARVARD
Raji ac | Sciencx Tuesday August 19, 2025 » Kotlin:’Generics’ Questions., viewed ,<https://www.scien.cx/2025/08/19/kotlingenerics-questions/>
VANCOUVER
Raji ac | Sciencx - » Kotlin:’Generics’ Questions. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/08/19/kotlingenerics-questions/
CHICAGO
" » Kotlin:’Generics’ Questions." Raji ac | Sciencx - Accessed . https://www.scien.cx/2025/08/19/kotlingenerics-questions/
IEEE
" » Kotlin:’Generics’ Questions." Raji ac | Sciencx [Online]. Available: https://www.scien.cx/2025/08/19/kotlingenerics-questions/. [Accessed: ]
rf:citation
» Kotlin:’Generics’ Questions | Raji ac | Sciencx | 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.

You must be logged in to translate posts. Please log in or register.