☕ Kotlin Cheat Sheet
Null safety, data classes, coroutines, Flow, sealed classes and more.
📦 Types & Null Safety
Variables
val name = "Kotlin" // immutable (val) var count = 0 // mutable (var) val pi: Double = 3.14 val inferred = "hello" // String inferred const val MAX = 100 // compile-time constant
Null safety
var name: String = "Feem" // non-null
var city: String? = null // nullable
// Safe call
city?.length // Int? (null if city is null)
city?.uppercase()
// Elvis operator
val len = city?.length ?: 0 // default if null
// Non-null assertion (throws NPE if null)
val len2 = city!!.length
// Smart cast
if (city != null) {
println(city.length) // auto-cast to String
}
// Let with null check
city?.let { c -> println(c.uppercase()) }String templates
val msg = "Hello, $name!"
val calc = "Result: ${2 + 2}"
val multi = """
|Multi-line
|string
""".trimMargin()
// String functions
"hello".uppercase()
"HELLO".lowercase()
"hello".capitalize() // deprecated, use replaceFirstChar
" hi ".trim()
"hello world".split(" ")
"hello".startsWith("he")
"hello".contains("ell")📚 Collections
List, MutableList
val list = listOf(1, 2, 3) // immutable
val mList = mutableListOf(1, 2, 3) // mutable
mList.add(4)
mList.addAll(listOf(5, 6))
mList.remove(1)
mList.removeAt(0)
mList[0] = 99
// Functional ops
list.map { it * 2 } // [2, 4, 6]
list.filter { it > 1 } // [2, 3]
list.find { it > 1 } // first match or null
list.first { it > 1 } // first match or throw
list.firstOrNull { it > 1 } // first or null
list.any { it > 2 } // true
list.all { it > 0 } // true
list.none { it > 10 } // true
list.count { it > 1 } // 2
list.sumOf { it } // 6
list.reduce { acc, e -> acc + e } // 6
list.fold(0) { acc, e -> acc + e } // 6
list.flatMap { listOf(it, it * 2) } // [1,2, 2,4, 3,6]
list.sortedBy { it }
list.groupBy { it % 2 } // {1=[1,3], 0=[2]}Map & Set
val map = mapOf("a" to 1, "b" to 2)
val mMap = mutableMapOf("a" to 1)
mMap["c"] = 3
mMap.remove("a")
mMap["missing"] // null
mMap.getOrDefault("x", 0)
mMap.getOrPut("y") { 99 }
mMap.keys; mMap.values; mMap.entries
val set = setOf(1, 2, 3, 3) // {1,2,3}
val mSet = mutableSetOf(1, 2)Ranges & destructuring
val range = 1..10 // 1 to 10
val until = 1 until 10 // 1 to 9
val step = 1..10 step 2 // 1,3,5,7,9
val down = 10 downTo 1 // 10,9...1
for (i in 1..5) print(i)
if (5 in 1..10) println("in range")
// Destructuring
val (a, b, c) = Triple(1, 2, 3)
val (x, y) = Pair("hello", 42)
val (key, value) = mapEntry🔧 Functions
Function syntax
// Regular function
fun add(a: Int, b: Int): Int = a + b
// Default params & named args
fun greet(name: String, greeting: String = "Hello") {
println("$greeting, $name!")
}
greet(greeting = "Hi", name = "Feem")
// Vararg
fun sum(vararg nums: Int): Int = nums.sum()
sum(1, 2, 3)
// Single-expression
fun square(n: Int) = n * n
// Higher-order
fun transform(list: List<Int>, fn: (Int) -> Int) = list.map(fn)
transform(listOf(1,2,3)) { it * 2 }Lambda & inline
val double: (Int) -> Int = { it * 2 }
val add: (Int, Int) -> Int = { a, b -> a + b }
// Trailing lambda
list.sortedWith(compareBy { it.name })
// Inline functions (avoid lambda overhead)
inline fun <T> measureTime(block: () -> T): T {
val start = System.currentTimeMillis()
val result = block()
println("Time: ${System.currentTimeMillis() - start}ms")
return result
}
// Typealias
typealias Callback<T> = (T) -> Unit
typealias Predicate = (String) -> Boolean🏗️ OOP
Class basics
class User(
val name: String,
var age: Int,
private val email: String
) {
// Secondary constructor
constructor(name: String) : this(name, 0, "")
init { println("Created: $name") }
// Property with getter/setter
var displayName: String = name
get() = field.uppercase()
set(value) { field = value.trim() }
// Companion object (like static)
companion object {
fun create(name: String) = User(name, 0, "")
const val MAX_AGE = 150
}
}
// Inheritance
open class Animal(val name: String) {
open fun speak() = println("...")
}
class Dog(name: String) : Animal(name) {
override fun speak() = println("$name: Woof!")
}Object & Singleton
// Singleton
object AppConfig {
val apiUrl = "https://api.example.com"
fun init() { println("Init") }
}
AppConfig.init()
// Anonymous object
val listener = object : ClickListener {
override fun onClick() { println("clicked") }
}Interface
interface Repository<T> {
suspend fun getAll(): List<T>
suspend fun getById(id: String): T?
suspend fun save(item: T)
suspend fun delete(id: String)
// Default implementation
suspend fun exists(id: String) = getById(id) != null
}
class UserRepositoryImpl(
private val remote: UserRemoteDataSource,
private val local: UserLocalDataSource,
) : UserRepository<User> {
override suspend fun getAll() = remote.fetchAll()
// ...
}📋 Data Classes
Data class
data class User(
val id: String,
val name: String,
val email: String,
val age: Int = 0
)
// Auto-generated: equals, hashCode, toString, copy
val user = User("1", "Feem", "feem@email.com")
val updated = user.copy(name = "New Name", age = 31)
// Destructure
val (id, name, email) = user🔒 Sealed Classes
Sealed class for Result
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable, val msg: String = exception.message ?: "") : Result<Nothing>()
object Loading : Result<Nothing>()
}
// Usage — exhaustive when
when (result) {
is Result.Success -> showData(result.data)
is Result.Error -> showError(result.msg)
is Result.Loading -> showLoader()
}
// UiState
sealed class UiState<out T> {
object Idle : UiState<Nothing>()
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}⏳ Coroutines
Coroutine basics
// Launch (fire & forget)
viewModelScope.launch {
val user = userRepo.getUser("1")
_uiState.value = UiState.Success(user)
}
// Async (returns Deferred)
val deferred = viewModelScope.async {
userRepo.getUser("1")
}
val user = deferred.await()
// Parallel
val userJob = async { userRepo.getUser("1") }
val postsJob = async { postRepo.getPosts("1") }
val user = userJob.await()
val posts = postsJob.await()Dispatchers
Dispatchers.Main // UI thread (Android)
Dispatchers.IO // I/O ops (network, disk)
Dispatchers.Default // CPU intensive
Dispatchers.Unconfined
withContext(Dispatchers.IO) {
// Run on IO thread
val data = api.fetchData()
withContext(Dispatchers.Main) {
// Switch back to Main
updateUI(data)
}
}Exception handling
// try-catch in suspend fun
suspend fun safeLoad(): Result<User> = runCatching {
userRepo.getUser("1")
}.fold(
onSuccess = { Result.Success(it) },
onFailure = { Result.Error(it) }
)
// CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, e ->
Log.e("TAG", "Error: $e")
}
viewModelScope.launch(handler) { ... }
// SupervisorScope (one child failure doesn't cancel others)
supervisorScope {
launch { fetchUser() }
launch { fetchPosts() }
}🌊 Kotlin Flow
Flow basics
// Cold flow
fun countFlow(): Flow<Int> = flow {
for (i in 1..5) {
delay(100)
emit(i)
}
}
// Hot flows
val _state = MutableStateFlow(UiState.Idle)
val state: StateFlow<UiState> = _state.asStateFlow()
val _events = MutableSharedFlow<UiEvent>()
val events: SharedFlow<UiEvent> = _events.asSharedFlow()
// Emit
_state.value = UiState.Loading
_events.emit(UiEvent.ShowToast("Saved!"))Flow operators
flow
.map { it * 2 }
.filter { it > 4 }
.take(3)
.onEach { log(it) }
.debounce(300) // debounce
.distinctUntilChanged() // skip duplicates
.catch { e -> emit(defaultValue) }
.onCompletion { println("done") }
.flowOn(Dispatchers.IO) // switch context
.collect { value -> handleValue(value) }
// Combine two flows
combine(flow1, flow2) { a, b -> a + b }.collect { ... }
// Zip
flow1.zip(flow2) { a, b -> Pair(a, b) }.collect { ... }Collect in ViewModel + Compose
// In ViewModel — convert to StateFlow
val uiState: StateFlow<HomeUiState> = userRepo
.getUserStream()
.map { HomeUiState.Success(it) }
.catch { emit(HomeUiState.Error(it.message ?: "")) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000L),
initialValue = HomeUiState.Loading
)
// In Compose
val uiState by vm.uiState.collectAsState()🔌 Extension Functions
Extension functions & properties
// Extension function
fun String.isEmail() = contains('@') && contains('.')
fun Int.isEven() = this % 2 == 0
fun <T> List<T>.secondOrNull() = if (size >= 2) this[1] else null
// Extension property
val String.initials: String
get() = split(" ").mapNotNull { it.firstOrNull()?.toString() }.joinToString("")
// Usage
"hello@world.com".isEmail() // true
42.isEven() // true
"John Doe".initials // "JD"🎯 Scope Functions
let, run, with, apply, also
// let — transform, null-safe, returns lambda result
val upper = name?.let { it.uppercase() }
// run — scope block, returns lambda result
val result = user.run {
copy(name = name.trim(), email = email.lowercase())
}
// with — like run but non-extension, returns lambda
val formatted = with(user) {
"Name: $name, Age: $age"
}
// apply — configure object, returns the object
val user = User().apply {
name = "Feem"
age = 30
email = "feem@email.com"
}
// also — side effects, returns the object
val list = mutableListOf(1, 2, 3)
.also { Log.d("TAG", "List: $it") }
.also { analytics.track("list_created") }