Bookmark this page. Use Ctrl+F (or Cmd+F on Mac) to find what you need. This cheat sheet covers Kotlin syntax from basics to coroutines. Try examples live at play.kotlinlang.org.
Last updated: April 2026
Variables#
| Syntax | Description |
|---|
val name = "Alex" | Immutable (read-only) — cannot reassign |
var count = 0 | Mutable — can reassign |
val age: Int = 25 | Explicit type annotation |
const val PI = 3.14 | Compile-time constant (top-level or object only) |
val name = "Alex" // type inferred as String
var score = 0 // type inferred as Int
score = 10 // OK — var can be reassigned
// name = "Sam" // ERROR — val cannot be reassigned
Basic Types#
| Type | Example | Notes |
|---|
Int | 42 | 32-bit integer |
Long | 42L | 64-bit integer |
Double | 3.14 | 64-bit decimal |
Float | 3.14f | 32-bit decimal |
Boolean | true, false | |
String | "hello" | Immutable |
Char | 'A' | Single character |
Type Conversions#
val x: Int = 42
val d: Double = x.toDouble() // 42.0
val s: String = x.toString() // "42"
val i: Int = "123".toInt() // 123
val safe: Int? = "abc".toIntOrNull() // null
Null Safety#
| Syntax | Description |
|---|
String? | Nullable type — can hold null |
name?.length | Safe call — returns null if name is null |
name ?: "default" | Elvis operator — use default if null |
name!! | Not-null assertion — throws if null (avoid this) |
name?.let { ... } | Execute block only if not null |
val name: String? = null
val len = name?.length // null (no crash)
val safe = name ?: "Unknown" // "Unknown"
// val crash = name!!.length // throws NullPointerException
String Templates#
val name = "Alex"
println("Hello $name") // Hello Alex
println("Length: ${name.length}") // Length: 4
println("Sum: ${2 + 3}") // Sum: 5
Functions#
// Regular function
fun greet(name: String): String {
return "Hello $name"
}
// Single-expression function
fun greet(name: String) = "Hello $name"
// Default parameters
fun greet(name: String = "World") = "Hello $name"
// Named arguments
greet(name = "Alex")
// Unit return type (void equivalent)
fun log(message: String) {
println(message)
}
Control Flow#
if / else (is an expression)#
val max = if (a > b) a else b
when (replaces switch)#
when (x) {
1 -> println("one")
2, 3 -> println("two or three")
in 4..10 -> println("between 4 and 10")
is String -> println("it is a string")
else -> println("something else")
}
// Guard conditions in when (Kotlin 2.2+)
when (val result = fetchData()) {
is Result.Success if result.data.isNotEmpty() -> show(result.data)
is Result.Success -> showEmpty()
is Result.Error -> showError(result.message)
}
// when as expression
val label = when {
score >= 90 -> "A"
score >= 80 -> "B"
else -> "C"
}
Loops#
for (i in 1..5) { } // 1, 2, 3, 4, 5
for (i in 1 until 5) { } // 1, 2, 3, 4 (or 1..<5)
for (i in 1..<5) { } // 1, 2, 3, 4 (open-ended range, Kotlin 1.8+)
for (i in 5 downTo 1) { } // 5, 4, 3, 2, 1
for (i in 0..10 step 2) { } // 0, 2, 4, 6, 8, 10
for (item in list) { } // iterate a collection
list.forEachIndexed { index, item -> }
Collections#
| Type | Create | Mutable Version |
|---|
| List | listOf(1, 2, 3) | mutableListOf(1, 2, 3) |
| Set | setOf(1, 2, 3) | mutableSetOf(1, 2, 3) |
| Map | mapOf("a" to 1) | mutableMapOf("a" to 1) |
Common Operations#
val numbers = listOf(1, 2, 3, 4, 5)
numbers.filter { it > 2 } // [3, 4, 5]
numbers.map { it * 2 } // [2, 4, 6, 8, 10]
numbers.first() // 1
numbers.last() // 5
numbers.firstOrNull { it > 10 } // null
numbers.any { it > 3 } // true
numbers.all { it > 0 } // true
numbers.count { it % 2 == 0 } // 2
numbers.sum() // 15
numbers.sorted() // [1, 2, 3, 4, 5]
numbers.reversed() // [5, 4, 3, 2, 1]
numbers.distinct() // remove duplicates
numbers.take(3) // [1, 2, 3]
numbers.drop(2) // [3, 4, 5]
numbers.groupBy { it % 2 } // {1=[1,3,5], 0=[2,4]}
numbers.associate { it to it * it } // {1=1, 2=4, 3=9, ...}
numbers.flatMap { listOf(it, it * 10) } // [1,10,2,20,...]
Map Operations#
val map = mapOf("a" to 1, "b" to 2)
map["a"] // 1
map.getOrDefault("c", 0) // 0
map.keys // [a, b]
map.values // [1, 2]
map.entries // [a=1, b=2]
map + ("c" to 3) // new map with c added
Lambdas#
val double = { x: Int -> x * 2 }
double(5) // 10
// Single parameter uses "it"
val numbers = listOf(1, 2, 3)
numbers.filter { it > 1 }
// Trailing lambda
numbers.fold(0) { acc, n -> acc + n }
Classes#
// Data class — equals, hashCode, toString, copy generated
data class User(val name: String, val age: Int)
val user = User("Alex", 25)
val copy = user.copy(age = 26)
// Enum class
enum class Color { RED, GREEN, BLUE }
// Sealed class — restricted hierarchy
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
data object Loading : Result()
}
// Object — singleton
object Database {
fun connect() { }
}
// Companion object — static-like members
class MyClass {
companion object {
fun create(): MyClass = MyClass()
}
}
Extension Functions#
fun String.addExclamation() = "$this!"
"Hello".addExclamation() // "Hello!"
fun List<Int>.secondOrNull(): Int? = if (size >= 2) this[1] else null
Scope Functions#
| Function | Object ref | Return | Use case |
|---|
let | it | Lambda result | Null checks, transformations |
run | this | Lambda result | Object config + compute result |
with | this | Lambda result | Group calls on an object |
apply | this | Object itself | Object configuration |
also | it | Object itself | Side effects (logging, validation) |
// let — execute block if not null
val length = name?.let { it.length }
// apply — configure a builder/mutable object
val paint = Paint().apply {
color = Color.RED
strokeWidth = 4f
}
// also — side effects
val list = mutableListOf(1, 2).also { println("Before: $it") }
// run — compute a result
val result = service.run {
connect()
fetchData()
}
// with — group calls
val info = with(user) {
"$name is $age years old"
}
Coroutines Basics#
// Launch — fire and forget
scope.launch {
val data = fetchData() // suspend function
updateUi(data)
}
// Async — returns a Deferred (future)
val deferred = async { fetchData() }
val result = deferred.await()
// Parallel execution
coroutineScope {
val a = async { fetchA() }
val b = async { fetchB() }
println("${a.await()} ${b.await()}")
}
// Switch context
withContext(Dispatchers.IO) {
// Run on background thread
readFile()
}
// Flow — reactive stream
fun numbers(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i)
}
}
// collect is a suspend function — must be called inside a coroutine
scope.launch {
numbers().collect { println(it) }
}
| Dispatcher | Use case |
|---|
Dispatchers.Main | UI updates |
Dispatchers.IO | Network, database, file I/O |
Dispatchers.Default | CPU-heavy computation |
Smart Casts#
// Kotlin auto-casts after a type check — no explicit cast needed
fun printLength(x: Any) {
if (x is String) {
println(x.length) // x is auto-cast to String
}
}
// Works with when too
when (result) {
is Result.Success -> println(result.data)
is Result.Error -> println(result.message)
}
Value Classes#
// Wraps a value with zero runtime overhead (no extra object allocation)
@JvmInline
value class Email(val address: String)
val email = Email("alex@example.com")
// At runtime, this is just a String — no wrapper object
Multi-line Strings#
val json = """
{
"name": "Alex",
"age": 25
}
""".trimIndent()
val sql = """
SELECT * FROM users
WHERE age > 18
ORDER BY name
""".trimIndent()
Common Patterns#
Safe casting#
val x: Any = "hello"
val s: String? = x as? String // "hello"
val i: Int? = x as? Int // null (no crash)
Destructuring#
val (name, age) = User("Alex", 25)
val (key, value) = mapEntry
takeIf / takeUnless#
val positiveNumber = number.takeIf { it > 0 } // number or null
val nonBlank = name.takeUnless { it.isBlank() } // name or null
// Chain with Elvis
val port = config.getPort().takeIf { it in 1..65535 } ?: 8080
Lazy initialization#
val heavy: String by lazy {
println("Computed!")
"result"
}
Common Mistakes#
Mutable vs immutable collections — listOf() returns a read-only List. Use mutableListOf() if you need add() or remove(). Casting a List to MutableList is unsafe.
== vs === — == checks structural equality (like Java’s .equals()). === checks referential equality (same object in memory). Most of the time you want ==.
Nullable type confusion — String and String? are different types. A non-null String cannot hold null. If a function might return null, the return type must be String?.