Kotlin Cheatsheet
Generics & Extensions
Use this Kotlin reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Generic functions & classes
fun <T> List<T>.secondOrNull(): T? = if (size < 2) null else this[1] class Box<T>(val value: T) class Cache<K, V>(private val map: MutableMap<K, V> = mutableMapOf()) { operator fun get(k: K): V? = map[k] operator fun set(k: K, v: V) { map[k] = v } }
Constraints:
fun <T : Comparable<T>> maxOf3(a: T, b: T, c: T) = maxOf(a, maxOf(b, c)) fun <T> pickTop(items: List<T>) where T : Comparable<T>, T : Cloneable = items.max()
Variance: in / out / star
| Modifier | Meaning | Example |
|---|---|---|
out T | covariant — T only produced (return positions) | List<out E>, Producer<out T> |
in T | contravariant — T only consumed (param positions) | Comparator<in T> |
| invariant | default — exact type required | MutableList<T> |
* | star projection — unknown argument | fun dump(xs: List<*>) |
val nums: List<Number> = listOf(1, 2) // ok: List is `out` (read-only) // val m: MutableList<Number> = mutableListOf(1) // error: MutableList invariant fun copyInto(dst: MutableList<in Int>, src: List<Int>) { dst.addAll(src) }
Use-site variance also works: fun fill(dest: Array<in String>).
reified type parameters
Generics are erased at runtime — unless the function is inline with reified T, which keeps the real type at the call site:
inline fun <reified T> Any?.isA(): Boolean = this is T inline fun <reified T> Json.parse(s: String): T = decodeFromString(s) val user: User = json.parse(body) // no Class<T> parameter needed "str".isA<String>() // true filterIsInstance<Circle>() // stdlib example of reified in action
Extension functions & properties
fun String.isPalindrome(): Boolean = this == reversed() fun <T> List<T>.penultimate(): T = this[size - 2] val String.wordCount: Int // extension property (no backing field) get() = trim().split(Regex("\\s+")).size fun StringBuilder?.orEmpty(): String = this?.toString() ?: "" // nullable receiver "level".isPalindrome() "one two three".wordCount
Extensions are resolved statically (by the declared type, not runtime type) and cannot override members. Scope them: extensions declared inside a class or imported explicitly keep namespaces clean.
Operator overloading
| Operator | Function name | Operator | Function name | |
|---|---|---|---|---|
a + b | plus | a[i] | get / set | |
a - b | minus | a in b | contains | |
a * b | times | a() | invoke | |
a += b | plusAssign | a..b | rangeTo | |
-a | unaryMinus | a < b | compareTo |
data class Vec(val x: Int, val y: Int) { operator fun plus(o: Vec) = Vec(x + o.x, y + o.y) operator fun times(k: Int) = Vec(x * k, y * k) } val v = Vec(1, 2) + Vec(3, 4) * 2
Comparators & sorting
data class Job(val start: Int, val end: Int, val pay: Int) jobs.sortedWith(compareBy({ it.end }, { it.start })) jobs.sortedWith(compareByDescending<Job> { it.pay }.thenBy { it.start }) jobs.maxWith(compareBy { it.pay }) val cmp: Comparator<Job> = compareBy { it.end } mutable.sortBy { it.end } // in-place variants: sortBy/sortWith/sort
typealias
typealias Grid = Array<IntArray> typealias Handler = (Request) -> Response typealias UserId = String // readability only — not a new type fun route(h: Handler) { }
For a real distinct type with near-zero overhead, use a value class: @JvmInline value class UserId(val raw: String).