☕ 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") }