Cheatsheet
A one-page Kotlin reference covering syntax + idioms you reach for every day.
Kotlin in one page
EXAMPLE
// ===== Variables =====
val name = "alice" // immutable
var count = 0 // mutable
const val PI = 3.14 // compile-time constant (top-level / companion)
// ===== Nullability =====
val name: String? = null
val length = name?.length ?: 0 // safe call + Elvis
name?.let { println(it) } // do something if non-null
name!!.length // assert non-null (avoid)
// ===== Strings =====
val greet = "Hello, $name (${name?.uppercase()})!"
val multi = """
|Line 1
|Line 2
""".trimMargin()
// ===== Collections =====
val xs = listOf(1, 2, 3)
val ms = mutableListOf(1, 2, 3); ms.add(4)
val set = setOf(1, 2, 2) // {1, 2}
val map = mapOf("a" to 1, "b" to 2)
map["a"]; map.getOrDefault("c", 0)
xs.map { it * 2 }
xs.filter { it.isOdd() }
xs.fold(0) { acc, n -> acc + n }
xs.sumOf { it * it }
xs.groupBy { it % 2 }
// ===== Functions =====
fun add(a: Int, b: Int = 0): Int = a + b
fun greet(name: String = "world") = "Hello, $name"
// Lambda
val double: (Int) -> Int = { it * 2 }
// Extension function
fun String.isPalindrome(): Boolean = this == this.reversed()
// ===== Classes =====
data class Order(val id: String, val customer: String, val totalCents: Long, val status: String = "new")
val o = Order("o1", "alice", 4995)
val paid = o.copy(status = "paid")
val (id, customer) = paid // destructuring
// Sealed type — exhaustive when()
sealed interface UiState
data object Loading : UiState
data class Loaded(val orders: List<Order>): UiState
data class Failed(val msg: String) : UiState
fun render(s: UiState) = when (s) {
Loading -> "..."
is Loaded -> "${s.orders.size} orders"
is Failed -> "Error: ${s.msg}"
}
// Inheritance
open class Vehicle(val wheels: Int)
class Car : Vehicle(4)
// Interface with default impl
interface Greeter {
fun greet(): String = "Hello"
}
// ===== Scope functions =====
val sb = StringBuilder().apply {
append("Hello, "); append("world")
}.toString()
val n = "hello".let { it.length }
val pair = run { 1 to 2 }
// ===== Coroutines =====
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
suspend fun fetch(): String { delay(100); return "data" }
runBlocking {
val a = async { fetch() }
val b = async { fetch() }
println(a.await() + b.await())
}
// Flow
fun stream(): Flow<Int> = flow {
for (i in 1..5) { delay(100); emit(i) }
}
// scope.launch { stream().collect { println(it) } }
// ===== Control flow =====
when (status) {
"new" -> doNew()
"paid" -> doPaid()
in listOf("shipped", "delivered") -> done()
is String -> println(status.length)
else -> unknown()
}
// ===== Tools =====
// gradle build / test
// kotlinc src/*.kt -include-runtime -d app.jar
// ./gradlew dependencies
// detekt + ktlint for static analysis + format
// ===== Patterns to internalise =====
// - data class for value types
// - sealed for exhaustive states
// - extension functions to extend without inheritance
// - scope functions (let / apply / run / with / also) for chained transforms
// - Flow for streams; suspend fun for single-value async
// - Default to val; reach for var when state mutation matters
// ===== Pitfalls =====
// - Overusing !! -> nullability lost; use ? + ?: or let
// - Mutable state in concurrent code without sync
// - Forgetting Dispatchers.IO on blocking calls -> stalls main thread
// - data class equality with floats / mutable fields
// - Coroutine scope leaks (use viewModelScope / lifecycleScope)
Why it matters
data class + sealed types + scope functions + coroutines is the Kotlin trinity. Reach for them by reflex — most concise, most testable, and easiest to read. The code that results is hard to confuse with "Java with less typing"; it is Kotlin idiomatic, and the difference is visible in every PR.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…