Kotlin Cheat Sheet
Every keyword, every pattern. Variables, functions, classes, coroutines, collections, Java interop and more.
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 |
|---|---|---|
|
|
32-bit |
|
|
|
64-bit |
|
|
|
64-bit |
|
|
|
32-bit |
|
|
|
1-bit |
|
|
|
16-bit |
|
|
|
N/A |
|
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, returnsnullotherwise -
?:(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
valorvar -
Cannot be
abstract,open,sealed, orinner
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,LinkedHashMapunder 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 |
|---|---|---|---|
|
|
|
Lambda result |
Null checks, transformations |
|
|
|
Lambda result |
Object init + compute result |
|
|
|
Lambda result |
Multiple operations, no extension |
|
|
|
The object |
Object configuration |
|
|
|
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 aStringBuilderDSL 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.