cheat sheet

Kotlin Cheat Sheet

Every keyword, every pattern. Variables, functions, classes, coroutines, collections, Java interop and more.

/ to focus
basic intermediate advanced

What Is Kotlin?

Kotlin is a statically typed, cross-platform programming language developed by JetBrains, officially released in 2016 and designed to run on the JVM with full interoperability with Java.

Google named it the preferred language for Android development at Google I/O 2017. Since then, adoption has been fast. Google's own data shows 70% of the top 1,000 apps on the Google Play Store are written in Kotlin, and 95% include at least some Kotlin code (JetBrains, 2024).

The Stack Overflow 2024 Developer Survey ranked Kotlin 4th most-loved programming language, with 58.2% developer satisfaction. Job postings requiring Kotlin expertise surged 30% year-over-year in 2024 (JetBrains Developer Ecosystem Survey).

Pinterest migrated their Android app from Java to Kotlin starting in 2018, reporting a 33% reduction in lines of code and 25% fewer crashes tied to null pointer exceptions by 2025.

Kotlin vs Java: Key Differences

Feature

Kotlin

Java

Null safety

Built into the type system

Relies on annotations and Optional

Boilerplate

Minimal (data classes, type inference)

Verbose (getters, setters, constructors)

Coroutines

Native support

Requires Virtual Threads (Java 21+)

Android support

Preferred, Kotlin-first APIs

Supported but no new AndroidX APIs since 2022

Interoperability

100% compatible with Java

N/A

Kotlin compiles to JVM bytecode, JavaScript, and native binaries via Kotlin/Native, making it a genuinely multi-target language. That said, most teams still use it primarily for mobile application development and JVM server-side work.


What Are the Basic Syntax Rules in Kotlin?

Kotlin syntax is intentionally lean. Semicolons are optional, the main() function is the program entry point, and newlines end statements by default.

fun main() {
    println("Hello, Kotlin")
}

Package and import declarations sit at the top of any file, identical in structure to Java but without requiring the class name to match the filename.

package com.example.app

import kotlin.math.sqrt

Output and Comments

Output functions:

  • print() outputs without a trailing newline

  • println() appends a newline after output

Comment syntax:

// Single-line comment

/*
   Multi-line comment
*/

Kotlin also supports doc comments with /** */, processed by Dokka (the official Kotlin documentation tool).

Program Entry Point

The main() function accepts optional command-line arguments via Array<String>.

fun main(args: Array<String>) {
    println(args[0])
}

No class wrapper required. This alone cuts several lines of boilerplate that Java developers write on autopilot.


What Are the Data Types and Variables in Kotlin?

Kotlin uses val for immutable and var for mutable variable declarations. The compiler infers types automatically, so explicit type annotations are optional in most cases.

val name: String = "Kotlin"   // explicit type
val version = 2.0              // inferred as Double
var count = 0                  // mutable

A 2024 analysis found that immutability (defaulting to val) led to a 50% reduction in unexpected application behavior in Kotlin codebases compared to equivalent mutable patterns.

Core Data Types

Type

Size

Example

Int

32-bit

val x: Int = 42

Long

64-bit

val y: Long = 9999L

Double

64-bit

val pi = 3.14

Float

32-bit

val f: Float = 3.14f

Boolean

1-bit

val flag = true

Char

16-bit

val c: Char = 'K'

String

N/A

val s = "text"

Nullable Types and Null Safety Operators

This is where Kotlin diverges from Java most clearly. A JetBrains 2023 survey found 79% of Kotlin developers reported a significant reduction in runtime errors after adopting Kotlin's null safety model.

By default, no variable can hold null. You opt into nullability explicitly with ?.

var name: String = "Kotlin"
name = null   // Compilation error

var nullable: String? = null  // Fine

3 operators for handling nullable types:

  • ?. (safe call): accesses a property only if non-null, returns null otherwise

  • ?: (Elvis operator): provides a fallback value when an expression is null

  • !! (non-null assertion): forces a non-null assumption; throws NPE if wrong

val length = nullable?.length        // null if nullable is null
val len = nullable?.length ?: 0      // 0 if nullable is null
val forced = nullable!!.length       // throws NPE if null

Avoid !! in production code unless you have an ironclad guarantee the value is non-null.


What Are the Operators in Kotlin?

Kotlin supports all standard arithmetic, comparison, and logical operators. The ones worth memorizing beyond the basics are the null-safety operators, which do real work in day-to-day Android development code.

Arithmetic and Comparison

// Arithmetic
val sum = 10 + 5      // 15
val mod = 10 % 3      // 1

// Comparison
val isEqual = (5 == 5)   // true
val notEqual = (5 != 3)  // true

Structural equality in Kotlin uses == (compares values). Referential equality uses === (compares object identity). This flips the mental model for Java developers who used == for reference comparison.

Logical Operators

val a = true && false   // false (AND)
val b = true || false   // true  (OR)
val c = !true           // false (NOT)

Null-Safety Operators (Practical Reference)

?. safe call:

val user: User? = getUser()
val email = user?.email   // null if user is null

?: Elvis operator:

val displayName = user?.name ?: "Guest"

!! non-null assertion:

val name = user!!.name   // throws KotlinNullPointerException if user is null

Kotlin also supports operator overloading via named functions like plus(), minus(), times(). Most developers don't need this unless building domain-specific libraries or math-heavy code.


What Are the Control Flow Statements in Kotlin?

Kotlin's control flow covers if, when, for, while, and do-while. The key difference from Java: if and when are expressions, meaning they return values.

The when Expression: Syntax and Use Cases

when replaces Java's switch. It's cleaner and far more capable.

val day = 3

val name = when (day) {
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    else -> "Other"
}

when without an argument acts as a chain of if-else expressions:

when {
    x > 100 -> println("Big")
    x > 50  -> println("Medium")
    else    -> println("Small")
}

68% of developers in a 2024 community survey found when expressions significantly easier to reason about than switch chains for state management (JetBrains Developer Community).

Ranges in for Loops

Kotlin's range syntax is one of those small things that makes loops genuinely readable.

for (i in 1..10)        // 1 to 10 inclusive
for (i in 1 until 10)   // 1 to 9 (exclusive end)
for (i in 10 downTo 1)  // reverse
for (i in 1..10 step 2) // 1, 3, 5, 7, 9

Iterating over a collection:

val fruits = listOf("apple", "banana", "cherry")
for (fruit in fruits) {
    println(fruit)
}

while and do-while follow the same syntax as Java. break and continue work identically. Labeled returns (break@label) let you exit nested loops without a flag variable.

outer@ for (i in 1..5) {
    for (j in 1..5) {
        if (j == 3) break@outer
    }
}

What Are the Functions in Kotlin?

Functions in Kotlin are declared with the fun keyword. They're first-class: you can store them in variables, pass them as arguments, and return them from other functions. That makes them the foundation of Kotlin's functional programming capabilities.

JetBrains' Developer Ecosystem Survey 2024 found that 75% of Kotlin users express satisfaction with the language, and higher-order functions rank consistently among the most-valued features.

Lambda Expressions and Anonymous Functions

Basic lambda syntax:

val square = { x: Int -> x * x }
println(square(4))   // 16

Passing a lambda as a function argument:

val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }   // [2, 4, 6, 8, 10]

it is the implicit name for a single-parameter lambda. Use an explicit name when the logic is complex enough that it hurts readability.

Higher-order function declaration:

fun operate(x: Int, y: Int, op: (Int, Int) -> Int): Int {
    return op(x, y)
}

val result = operate(3, 4) { a, b -> a + b }   // 7

Extension Functions

Extension functions add behavior to existing classes without modifying their source code or inheriting from them. This is probably the feature I've seen confuse the most Java developers when they first read Kotlin code.

fun String.shout(): String = this.uppercase() + "!!!"

println("hello".shout())   // HELLO!!!

Key details:

  • Defined outside the class they extend

  • No access to private members of the receiver class

  • Resolved statically (not polymorphically)

  • Used heavily in Kotlin's standard library (e.g., .filter {}, .map {}, .forEach {})

Extension functions are the reason Kotlin's standard library reads so cleanly compared to Java utility classes.

Default and Named Arguments

Default parameter values cut down on function overloading.

fun greet(name: String, greeting: String = "Hello") {
    println("$greeting, $name!")
}

greet("Alice")               // Hello, Alice!
greet("Bob", "Hi")           // Hi, Bob!
greet(greeting = "Hey", name = "Carol")  // named args, any order

vararg for variable-length argument lists:

fun sum(vararg numbers: Int): Int = numbers.sum()

sum(1, 2, 3, 4)   // 10

What Are the Classes and Objects in Kotlin?

Kotlin's class system is compact. A lot of what Java requires dozens of lines for (getters, setters, equals(), hashCode(), toString()) Kotlin handles in one or two keywords.

Netflix adopted Kotlin for their Android app in 2020. By 2025, 100% of new Android code was written in Kotlin, with coroutines and data classes cited as the primary productivity gains.

Data Classes

A data class in one line:

data class User(val name: String, val age: Int)

The compiler auto-generates: equals(), hashCode(), toString(), copy(), and componentN() functions for destructuring.

val user1 = User("Alice", 30)
val user2 = user1.copy(age = 31)
println(user1 == user2)   // false

Data class rules:

  • Primary constructor must have at least 1 parameter

  • All parameters must be val or var

  • Cannot be abstract, open, sealed, or inner

Sealed Classes and When Expressions

Sealed classes define a restricted type hierarchy. All subclasses must be in the same package (Kotlin 1.5+) or the same file (earlier versions).

sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()

when with a sealed class becomes exhaustive automatically.

fun handle(result: Result) = when (result) {
    is Success -> println(result.data)
    is Error   -> println(result.message)
    is Loading -> println("Loading...")
}

No else branch needed. The compiler enforces coverage of all subtypes. This pattern is used constantly in Android development for UI state management with ViewModel and Jetpack Compose.

Companion Objects

Kotlin has no static keyword. Companion objects fill that role.

class MathUtils {
    companion object {
        fun square(x: Int) = x * x
        const val PI = 3.14159
    }
}

MathUtils.square(5)   // 25
MathUtils.PI          // 3.14159

3 things to know about companion objects:

  • One per class (unlike regular objects, which can be many)

  • Can implement interfaces, making them more flexible than Java's static methods

  • Accessible from Java via MathUtils.Companion.square(5)

The what is Kotlin companion object deep-dive covers factory patterns and interface implementations in detail.

What Is Inheritance and Interfaces in Kotlin?

Kotlin classes are final by default. You explicitly mark a class with open to allow inheritance. This design prevents accidental subclassing, which is a genuinely common source of bugs in large Java codebases.

The override keyword is mandatory. Unlike Java's implicit overriding, Kotlin forces you to declare your intent.

open class Animal(val name: String) {
    open fun sound() = "..."
}

class Dog(name: String) : Animal(name) {
    override fun sound() = "Woof"
}

Square (Block) was one of the earliest major Kotlin adopters for Android. Their engineering team highlighted sealed classes and explicit override rules as key factors in reducing unintended behavior in their model layer.

Abstract Classes vs Interfaces: 3 Key Differences

Dimension

Abstract Class

Interface

State

Can hold state (properties with values)

No state (no backing fields)

Constructors

Supported

Not supported

Inheritance

Single class only

Multiple interfaces allowed

When to use which:

  • Use an interface when defining a contract for unrelated classes or when you need multiple inheritance

  • Use an abstract class when you need to share state or constructor logic across closely related classes

How Overriding Works

open is required at every level. A method in a subclass is final by default after being overridden. To allow further overriding down the chain, you must mark it open again.

open class Shape {
    open fun draw() = println("Drawing shape")
}

open class Circle : Shape() {
    override fun draw() = println("Drawing circle")   // final by default
}

Interfaces in Kotlin support default method implementations, so a class implementing 2 interfaces with the same default method must explicitly resolve the conflict using super<InterfaceName>.method().


What Are the Collections in Kotlin?

Kotlin collections are immutable by default. listOf(), setOf(), and mapOf() return read-only views. You opt into mutability with mutableListOf(), mutableSetOf(), and mutableMapOf().

Immutable collections are thread-safe and prevent accidental modifications across different parts of a codebase, a practical advantage in multi-threaded environments (kotlinx.collections.immutable library, JetBrains 2025).

Core Collection Types

Immutable variants:

  • listOf("a", "b", "c") — ordered, allows duplicates

  • setOf(1, 2, 3) — unordered, no duplicates

  • mapOf("key" to "value") — key-value pairs

Mutable variants:

  • mutableListOf(), mutableSetOf(), mutableMapOf()

  • Backed by ArrayList, LinkedHashSet, LinkedHashMap under the hood

Common Collection Operations

JetBrains data shows over 50% of Kotlin users have shifted toward functional constructs like collection operations, reducing common errors tied to imperative loops (MoldStud, 2024).

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.find { it > 3 }           // 4
numbers.any { it > 4 }            // true
numbers.all { it > 0 }            // true
numbers.forEach { print(it) }     // 12345
numbers.reduce { acc, i -> acc + i }  // 15

groupBy for categorization:

val words = listOf("apple", "ant", "banana", "blueberry")
val grouped = words.groupBy { it.first() }
// {a=[apple, ant], b=[banana, blueberry]}

Kotlin collections are 100% interoperable with Java collections, meaning you can pass a Kotlin List directly to any Java method expecting java.util.List.


What Are Coroutines in Kotlin?

Coroutines are Kotlin's answer to async programming. They're lightweight threads managed by the Kotlin runtime, not the OS. You can run hundreds of thousands of them concurrently without the memory overhead of creating equivalent OS threads.

A 2024 Journal of Computer Sciences analysis found that coroutines and thread pools exhibit the highest performance among concurrency techniques in Kotlin for I/O operations. A JetBrains report found that 40% of developers actively use coroutines to simplify async work (MoldStud, 2024).

suspend Functions

A suspend function can pause execution without blocking the calling thread. It can only be called from a coroutine or another suspend function.

suspend fun fetchData(): String {
    delay(1000)   // non-blocking delay
    return "Result"
}

Key rule: delay() in a coroutine vs Thread.sleep() in Java. Both pause for 1 second. delay() releases the thread to do other work. Thread.sleep() blocks it completely.

launch vs async

launch: fire-and-forget, returns a Job

async: returns a Deferred<T>, use .await() to get the result

// launch: no return value needed
val job = CoroutineScope(Dispatchers.IO).launch {
    performBackgroundWork()
}

// async: result required
val deferred = CoroutineScope(Dispatchers.IO).async {
    fetchUserData()
}
val user = deferred.await()

3 core dispatchers:

  • Dispatchers.Main — UI thread (Android)

  • Dispatchers.IO — disk and network operations

  • Dispatchers.Default — CPU-intensive work

Square's 2025 engineering blog reported that Kotlin's coroutine-based networking layer reduced timeout-related bugs by 50% compared to their previous Java RxJava implementation.


What Are Kotlin Scope Functions?

Scope functions execute a block of code on an object within a temporary scope. There are exactly 5: let, run, with, apply, also. They differ on 2 axes: how you reference the object inside the block (it vs this), and what the block returns (the object vs the lambda result).

JetBrains documentation notes apply is the most common scope function in Android development, primarily for object configuration and intent setup.

Scope Function Comparison

Function

Object reference

Return value

Best for

let

it

Lambda result

Null checks, transformations

run

this

Lambda result

Object init + compute result

with

this

Lambda result

Multiple operations, no extension

apply

this

The object

Object configuration

also

it

The object

Side effects, logging

Practical Usage Patterns

apply for object configuration:

val intent = Intent(context, MainActivity::class.java).apply {
    putExtra("USER_ID", 123)
    putExtra("KEY", "value")
}

let for null-safe operations:

val email: String? = getEmail()
email?.let {
    sendNotification(it)
}

also for debugging chains:

val result = fetchData()
    .also { println("Raw result: $it") }
    .filter { it.isNotEmpty() }

A common mistake is nesting apply inside let inside run. Readable at one level deep. Tricky at two. Avoid 3+ nested scope functions.


What Is String Handling in Kotlin?

Kotlin strings are immutable. The String class has no methods that modify the string in place. Every transformation returns a new string. For repeated concatenation inside loops, use StringBuilder.

67% of Kotlin users highlight coroutines and string templates as features that most reduce boilerplate compared to Java (JetBrains).

String Templates and Multiline Strings

String templates use $ for simple variables and ${} for expressions.

val name = "Kotlin"
val version = 2.0
println("$name version ${version + 0.1}")   // Kotlin version 2.1

Raw (multiline) strings use triple quotes. No escape sequences needed.

val json = """
    {
        "name": "Kotlin",
        "version": 2.0
    }
""".trimIndent()

trimIndent() removes the leading whitespace from each line based on the minimum indent.

Common String Functions

Comparison and search:

  • == compares value (structural equality)

  • === compares reference (rarely needed)

  • contains("text"), startsWith("K"), endsWith("n")

Transformation:

val s = "  hello world  "

s.trim()                      // "hello world"
s.uppercase()                 // "  HELLO WORLD  "
s.replace("world", "Kotlin")  // "  hello Kotlin  "
s.split(" ")                  // ["", "", "hello", "world", "", ""]
s.substring(2, 7)             // "hello"

StringBuilder for mutable operations:

val sb = StringBuilder()
sb.append("Hello")
sb.append(", ")
sb.append("Kotlin")
println(sb.toString())   // Hello, Kotlin

What Are Generics in Kotlin?

Generics let you write type-safe code that works across multiple types without duplication. On the JVM, generic type information is erased at runtime (type erasure). Kotlin provides reified to work around this limitation for inline functions.

Generic Functions and Classes

Generic function:

fun <T> printItem(item: T) {
    println(item)
}

printItem("Hello")   // T inferred as String
printItem(42)        // T inferred as Int

Generic class:

class Box<T>(val value: T)

val stringBox = Box("Kotlin")
val intBox = Box(100)

Variance: out and in

out (covariance): the type parameter is only produced, never consumed. Safe for reading.

in (contravariance): the type parameter is only consumed, never produced. Safe for writing.

class Producer<out T>(val value: T)    // can return T, cannot accept T
class Consumer<in T> {                 // can accept T, cannot return T
    fun consume(item: T) { }
}

Star projection <*> is the equivalent of Java's wildcard <?>. Use it when you need to work with a generic type but don't know or care about the actual type argument.

reified Type Parameters

The problem: in a normal generic function, you cannot do T::class at runtime because type information is erased.

The fix: mark the type parameter as reified and the function as inline.

inline fun <reified T> filterByType(list: List<Any>): List<T> {
    return list.filterIsInstance<T>()
}

val items = listOf(1, "hello", 2, "world", 3)
val strings = filterByType<String>(items)   // ["hello", "world"]

filterIsInstance<T>() in Kotlin's standard library uses exactly this pattern under the hood.


What Are the Most Common Kotlin Standard Library Functions?

The Kotlin standard library is packed with utility functions that replace common patterns Java developers handle with utility classes or verbose boilerplate. These are the ones you'll actually use in production code.

Kotlin's concise syntax and standard library features improve developer productivity by up to 30% compared to equivalent Java code, according to industry studies (MoldStud, 2025).

Property Delegation and Lazy Initialization

lazy {} defers computation until first access. The result is cached after the first call.

val config: AppConfig by lazy {
    loadConfigFromDisk()   // runs only on first access
}

by keyword for property delegation:

class User {
    var name: String by Delegates.observable("") { _, old, new ->
        println("Changed from $old to $new")
    }
}

takeIf and takeUnless for conditional chaining:

val result = value.takeIf { it > 0 }     // returns value if condition true, else null
val other  = value.takeUnless { it < 0 } // returns value if condition false, else null

Utility Functions

repeat(n) {} replaces counted for loops cleanly:

repeat(3) { println("Kotlin") }   // prints 3 times

measureTimeMillis {} for quick benchmarks:

val time = measureTimeMillis {
    performExpensiveOperation()
}
println("Took $time ms")

also, let, apply covered in scope functions. The 3 additional standard library functions worth knowing:

  • run {} (without receiver): creates a local scope and returns the last expression

  • with(obj) {}: calls multiple methods on the same object without repeating its name

  • buildString {}: constructs a string using a StringBuilder DSL internally

For a broader view of how these fit into real Android development workflows, Kotlin's official stdlib reference at kotlinlang.org covers all available extensions across collection, string, and I/O operations.

The concepts here connect directly to topics like what are Kotlin coroutines, Kotlin lambda functions, and what are Kotlin flows if you want to go deeper on async and reactive patterns. And if you're comparing language choices, the breakdown of Kotlin or Java for Android covers the production tradeoffs in detail.

For reference-style comparison with other languages, the Java cheat sheet is a useful side-by-side on JVM syntax differences.

FAQ on Kotlin

What is the difference between val and var in Kotlin?

val declares an immutable variable. Once assigned, it cannot be reassigned. var is mutable and can change at any point. Default to val unless you explicitly need reassignment. Most Kotlin codebases use val for roughly 80% of declarations.

How does Kotlin handle null safety?

Kotlin's type system separates nullable and non-nullable types at compile time. A regular String cannot hold null. A String? can. The safe call operator ?., the Elvis operator ?:, and the non-null assertion !! are the 3 tools for working with nullable references.

What are Kotlin coroutines and when should I use them?

Coroutines are lightweight, suspendable computations for async programming. Use them for network calls, database reads, and any I/O operation where you need a non-blocking approach. They run on CoroutineScope and use dispatchers like Dispatchers.IO or Dispatchers.Main to target specific threads.

What is a data class in Kotlin?

A data class auto-generates equals(), hashCode(), toString(), and copy() from its primary constructor parameters. One line replaces dozens in Java. Use it for model objects, API response types, and any class whose purpose is to hold data rather than define behavior.

What is the difference between launch and async in Kotlin coroutines?

launch is fire-and-forget. It returns a Job with no result. async returns a Deferred<T> and you call .await() to retrieve the value. Use launch for side effects and async when you need a result from a concurrent operation.

What are scope functions in Kotlin?

Kotlin has 5 scope functions: let, run, with, apply, and also. They execute a block of code on an object within a temporary scope. The differences come down to 2 things: whether the object is referenced as it or this, and whether the function returns the object or the lambda result.

How does Kotlin interoperate with Java?

Kotlin and Java are 100% interoperable on the JVM. You can call Java code from Kotlin and Kotlin code from Java without wrappers or adapters. Existing Java libraries, frameworks like Spring Boot, and Android SDK APIs all work directly. This is why most teams migrate incrementally rather than rewriting everything at once.

What is a sealed class in Kotlin?

A sealed class defines a restricted type hierarchy where all subclasses are known at compile time. Combined with when expressions, the compiler enforces exhaustive handling of every subtype. It is the standard pattern for representing UI state, network results, and any finite set of outcomes in Android development.

What is the by keyword used for in Kotlin?

by enables property and interface delegation. val config by lazy {} defers initialization until first access. class MyList : List<String> by delegate forwards all interface calls to another object. It replaces inheritance in many cases and keeps classes focused on a single responsibility.

How does Kotlin compare to Java for Android development?

Google made Kotlin the preferred language for Android at Google I/O 2017. Today, 95% of the top 1,000 Android apps include Kotlin code (JetBrains, 2024). Kotlin offers null safety, coroutines, and less boilerplate. For a full breakdown of production tradeoffs, the Kotlin or Java comparison covers the key decisions.