Kotlin Cheatsheet

Functions

Use this Kotlin reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Function forms

fun add(a: Int, b: Int): Int {        // block body — return type required if not Unit
    return a + b
}

fun square(x: Int) = x * x            // expression body — type inferred

fun log(msg: String): Unit { }        // Unit is optional to write

infix fun Int.pow(e: Int): Long { /* ... */ }   // 2 pow 10
tailrec fun gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)

Parameters are always read-only vals. Write explicit return types on public APIs.

Default & named arguments

fun greet(name: String, loud: Boolean = false, punct: String = "!") =
    if (loud) "HELLO, $name$punct" else "Hello, $name$punct"

greet("Ada")
greet("Ada", punct = "?")             // skip middle params by name
greet(name = "Ada", loud = true)

Defaults + named args replace most Java-style overloads (see @JvmOverloads for Java callers).

varargs

fun sumAll(vararg xs: Int) = xs.sum()

sumAll(1, 2, 3)
val arr = intArrayOf(4, 5)
sumAll(*arr)                          // spread operator

Lambdas & function types

val doubled = nums.map { it * 2 }              // `it` = single implicit param
val kept = nums.filter { n -> n % 2 == 0 }     // named param
nums.forEach { println(it) }

val op: (Int, Int) -> Int = { a, b -> a + b }  // function type
val nullableFn: ((Int) -> Int)? = null
fun apply3(f: (Int) -> Int) = f(3)             // higher-order
apply3(::square)                               // function reference
list.sortedBy(Point::x)                        // member reference

A trailing lambda moves outside the parentheses: items.fold(0) { acc, x -> acc + x }. The last expression in a lambda is its value; use return@map value for an early non-local-looking return.

Scope functions

The five stdlib scope functions differ only in how they expose the receiver and what they return:

FunctionObject isReturnsTypical use
letitlambda resulttransform, null-safe chains: x?.let { }
runthislambda resultconfigure + compute a result
with(x)thislambda resultgroup calls on an existing object
applythisthe objectobject configuration/builders
alsoitthe objectside effects (logging) mid-chain
val len = name?.let { it.trim().length } ?: 0

val client = HttpClient().apply {      // returns the client
    timeout = 5_000
    retries = 3
}

val result = with(stats) { "$mean ± $stddev" }

val user = loadUser(id)
    .also { log.debug("loaded {}", it) }
    .let { it.copy(active = true) }

val port = config.run { host.split(":").last().toInt() }

Rules of thumb: apply/also return the object (chain-friendly); let/run/with return the block result. Don't nest them — extract a function instead.

inline, noinline, crossinline

inline fun <T> measure(block: () -> T): T {
    val t0 = System.nanoTime()
    try { return block() } finally { println(System.nanoTime() - t0) }
}
  • inline copies the function + lambda bodies to the call site: no lambda allocation, and return inside the lambda exits the caller (non-local return).
  • noinline on a lambda param opts it out (so it can be stored/passed on).
  • crossinline forbids non-local returns (needed when the lambda runs in another context).
  • inline also unlocks reified type parameters (see Generics & Extensions).

Most stdlib collection operations (map, filter, forEach, scope functions) are inline — zero overhead.