Chanzmao ʕ•ᴥ•ʔ Bear Blog

Kotlin Generic Type Declarations: Common Patterns and Examples

Generics <T> allow you to write reusable, type-safe code without committing to a specific type upfront.

They’re commonly used in collections, classes, functions, interfaces, and function types throughout Kotlin.

This article walks through some of the most common generic type declaration patterns you’ll encounter in real-world Kotlin code.

List

Specify the type of elements stored in a list.

val names: List<String> = listOf("Alice", "Bob")

Only String values can be stored in this list.

Map<K, V>

Specify the types of keys and values.

val users: Map<Int, String> = mapOf(1 to "Alice")

K stands for key and V stands for value.

MutableList

A mutable list whose contents can be changed.

val names: MutableList<String> = mutableListOf()

Elements can be added, removed, or updated.

Nested Generics

Generics can be combined to represent more complex structures.

val cache: Map<String, List<Int>> = mapOf(
    "scores" to listOf(1, 2, 3)
)

This pattern is common when working with APIs and data models.

Generic Classes

A generic class can work with different types.

class Box<T>(val value: T)

val box = Box<String>("Hello")

The same class can be reused with any type.

Multiple Type Parameters

A class can define more than one type parameter.

class PairBox<K, V>(val key: K, val value: V)

val user = PairBox<Int, String>(1, "Alice")

This pattern is similar to how Map<K, V> works.

Generic Functions

The type is determined when the function is called.

fun <T> first(items: List<T>): T? = items.firstOrNull()

val result = first(listOf("A", "B"))

Generic functions make shared logic reusable across different types.

Generic Extension Functions

Extension functions can also be generic.

fun <T> List<T>.second(): T? = getOrNull(1)

This works with any List<T>.

Generic Interfaces

The implementation decides which type will be used.

interface Repository<T> {
    fun save(item: T)
}

class UserRepository : Repository<String>

This is a common pattern in repository and service APIs.

Function Types

Function types can also include generic types.

val formatter: (Int) -> String = { "Number: $it" }

This function takes an Int and returns a String.

Type Aliases

Give complex generic types a more readable name.

typealias Callback<T> = (T) -> Unit

val callback: Callback<String> = {
    println(it)
}

This improves readability and reduces repetition.

Generic Functional Interfaces

Useful when working with SAM conversion.

fun interface Transformer<T, R> {
    fun transform(value: T): R
}

val transformer: Transformer<Int, String> = Transformer {
    "Number: $it"
}

This allows lambdas to be used with strongly typed interfaces.

Nullable Generics

Allow nullable values inside generic types.

val names: List<String?> = listOf("Alice", null)

Notice the nullable type String?.

Star Projections

Use * when the generic type is unknown.

val items: List<*> = listOf("A", 1, true)

This is Kotlin’s equivalent of Java’s List<?>.

Covariance (out)

Used for producer types that only provide values.

val numbers: List<out Number>

Allows subtype values to be treated safely as a parent type.

Contravariance (in)

Used for consumer types that receive values.

val consumer: Comparable<in String>

Often appears in framework and library APIs.

Reified Type Parameters

Keep type information available at runtime.

inline fun <reified T> isType(value: Any): Boolean = value is T

isType<String>("Hello")

Frequently used in serialization and JSON libraries.

Type Constraints

Restrict which types can be used.

fun <T : Number> double(value: T): Double = value.toDouble() * 2

Only types that extend Number are allowed.

Multiple Constraints

Apply more than one requirement to a type parameter.

fun <T> process(value: T) where T : CharSequence, T : Comparable<T> {
}

Useful for advanced reusable APIs.

Summary

List<String>         // Collection element type
Map<K, V>            // Key and value types
Box<T>               // Generic class
fun <T>              // Generic function
Repository<T>        // Generic interface
(T) -> R             // Function type
Callback<T>          // Type alias
List<*>              // Unknown type
out T                // Covariance
in T                 // Contravariance
reified T            // Runtime type information
<T : Number>         // Type constraint

In day-to-day Android and Kotlin development, you’ll most often encounter List<T>, Map<K, V>, fun <T>, and function types such as (T) -> R. More advanced features like out, in, reified, and type constraints tend to appear when building reusable libraries and frameworks.

#Android #Kotlin #Programming #Syntax