askesis

Kotlin essentials: 50 cards on null safety, data classes and coroutines

Kotlin fundamentals with short snippets: null safety, val vs var, data and sealed classes, extension functions, scope functions and coroutines.

ProgrammingBeginner50 cardsFree

Practice now

Study without an account

0 of 50 seen0 graduated
String vs String? in Kotlin

Scheduled with the default Askesis timings. Progress is saved in this browser.

All 50 cards

  1. 1
    String vs String? in Kotlin
    String can never hold null; String? can. The compiler won’t let you assign null to a String, or call methods on a String? without handling the null case
  2. 2
    What does user?.address?.city evaluate to if address is null?
    null: a safe call ?. short-circuits to null instead of throwing
  3. 3
    The Elvis operator ?:
    Returns the left side if it isn’t null, otherwise the right side
    kotlin
    val name = input ?: "anonymous"
    val user = findUser(id) ?: return  // early exit
  4. 4
    What does !! do?
    Asserts that a value isn’t null and throws NullPointerException if it is. Treat every !! as a possible crash
  5. 5
    What does this print?
    kotlin
    val s: String? = null
    println(s?.length ?: -1)
    -1: s?.length is null, so the Elvis operator supplies -1
  6. 6
    What is a smart cast?
    After a check like if (s != null) or if (x is String), the compiler treats the value as the narrower type, with no explicit cast
    It doesn’t work on a mutable var property, because the value could change between the check and the use.
  7. 7
    Run a block only when a value isn’t null
    kotlin
    email?.let { sendWelcome(it) }
  8. 8
    Cast without risking a ClassCastException
    kotlin
    val n = value as? Int ?: 0
    as? returns null when the cast fails; plain as throws.
  9. 9
    What are platform types like String!?
    Types coming from Java code without nullability annotations. Kotlin lets you treat them as nullable or not, so a wrong guess can throw NullPointerException at runtime
  10. 10
    val vs var
    val is a read-only reference that can’t be reassigned; var can be. Prefer val
    val doesn’t make the object immutable: val xs = mutableListOf(1) can still add.
  11. 11
    val vs const val
    const val is a compile-time constant: a top-level or object member of a primitive type or String. A plain val can be computed at runtime
  12. 12
    String templates
    kotlin
    println("Hello, $name! You have ${cart.size} items")
    Use ${…} for anything beyond a simple name.
  13. 13
    Write a ternary in Kotlin
    kotlin
    val max = if (a > b) a else b
    Kotlin has no cond ? a : b operator; if and when are expressions that return values.
  14. 14
    A when expression
    kotlin
    val size = when (n) {
        0 -> "none"
        1, 2 -> "few"
        in 3..9 -> "some"
        else -> "many"
    }
  15. 15
    Ranges: 1..5, 1..<5, 5 downTo 1
    1..5 includes 5; 1..<5 (or 1 until 5) stops before 5; 5 downTo 1 counts down. Add step 2 to skip values
    The ..< operator is stable since Kotlin 1.9.
  16. 16
    == vs === in Kotlin
    == compares structurally by calling equals (and handles null); === checks whether both references point to the same object
  17. 17
    Default and named arguments
    kotlin
    fun greet(name: String, greeting: String = "Hello") =
        println("$greeting, $name")
    
    greet("Ada")
    greet(greeting = "Hi", name = "Ada")
    They replace most overloads and builder patterns.
  18. 18
    A single-expression function
    kotlin
    fun square(x: Int) = x * x
    The return type (Int) is inferred from the expression.
  19. 19
    Unit vs Nothing
    Unit is the return type of functions with no useful result (like void, but a real object); Nothing is the type of expressions that never complete, like throw and TODO()
  20. 20
    What does data class User(val name: String, val age: Int) generate?
    equals, hashCode, toString, copy, and component1() and component2() for destructuring, all based on the primary constructor’s properties
  21. 21
    What does this print?
    kotlin
    data class User(val name: String, val age: Int)
    println(User("Ada", 36))
    User(name=Ada, age=36)
    A regular class would print something like User@1b6d3586.
  22. 22
    Copy a data class with one property changed
    kotlin
    val older = user.copy(age = 37)
    copy is shallow: nested objects are shared, not cloned.
  23. 23
    Destructure a data class
    kotlin
    val (name, age) = user
    Destructuring is positional: it uses component1(), component2()… in constructor order, not property names.
  24. 24
    Can you subclass any Kotlin class?
    No. Classes and members are final by default: mark the class open to allow subclassing, and each method open to allow overriding it with override
  25. 25
    Declare properties directly in the constructor
    kotlin
    class Person(val name: String, var age: Int) {
        init {
            require(age >= 0) { "age must be >= 0" }
        }
    }
    val or var in the primary constructor creates a property; init blocks run as part of construction.
  26. 26
    Kotlin’s replacement for static members
    An object declaration is a singleton; a companion object inside a class holds members you call on the class, like User.create()
    kotlin
    class User private constructor(val name: String) {
        companion object {
            fun create(name: String) = User(name.trim())
        }
    }
  27. 27
    Model a closed set of results with a sealed interface
    kotlin
    sealed interface Result
    data class Success(val data: String) : Result
    data class Failure(val error: Throwable) : Result
    
    fun render(r: Result) = when (r) {
        is Success -> r.data
        is Failure -> "Error: ${r.error.message}"
    }
    The compiler knows every subtype, so when needs no else and flags missing cases.
  28. 28
    Sealed class vs enum class
    Each enum constant is a single instance; a sealed type’s subclasses are ordinary classes that can have many instances, each with its own state
  29. 29
    lateinit var vs by lazy
    lateinit var is a non-null property you assign later (reading it first throws); val x by lazy { … } computes its value on first access and caches it
    lateinit doesn’t work with primitive types like Int; lazy is thread-safe by default.
  30. 30
    What does internal visibility mean?
    Visible everywhere in the same module, such as one Gradle module, but not outside it
    Kotlin’s default visibility is public.
  31. 31
    Add a function to String without subclassing it
    kotlin
    fun String.shout() = uppercase() + "!"
    
    println("hi".shout()) // HI!
    Inside an extension function, this is the receiver.
  32. 32
    Are extension functions dispatched virtually?
    No. They are resolved statically from the declared type, can’t access private members, and lose to a member function with the same signature
  33. 33
    listOf vs mutableListOf
    listOf returns a read-only List (no add); mutableListOf returns a MutableList
    Read-only isn’t immutable: a List reference can point to a list that other code mutates.
  34. 34
    Get the sorted names of all adults
    kotlin
    val names = users
        .filter { it.age >= 18 }
        .map { it.name }
        .sorted()
    it is the implicit name of a lambda’s single parameter.
  35. 35
    What is trailing lambda syntax?
    When a function’s last parameter is a function, the lambda can go outside the parentheses, and the parentheses can be dropped if it is the only argument
    kotlin
    repeat(3) { println("hi") }
    names.forEach { println(it) }
  36. 36
    Group users by city
    kotlin
    val byCity: Map<String, List<User>> = users.groupBy { it.city }
    associateBy { it.id } builds a map with one value per key instead.
  37. 37
    When should you use asSequence()?
    For long chains over large collections: sequences process elements lazily, one at a time, instead of building an intermediate list after every step
    kotlin
    val firstBig = numbers.asSequence()
        .map { it * it }
        .first { it > 1_000 }
  38. 38
    What does this print?
    kotlin
    fun firstEven(xs: List<Int>): Int {
        xs.forEach { if (it % 2 == 0) return it }
        return -1
    }
    
    println(firstEven(listOf(1, 4, 6)))
    4: a bare return inside the lambda of an inline function like forEach returns from the enclosing function
    Use return@forEach to skip to the next element instead.
  39. 39
    The five scope functions at a glance
    let (it, returns the lambda result), run (this, lambda result), with (this, lambda result, not an extension), apply (this, returns the object), also (it, returns the object)
  40. 40
    Which scope function configures an object and returns it?
    kotlin
    val user = User().apply {
        name = "Ada"
        age = 36
    }
    apply runs the block with the object as this and returns the object itself.
  41. 41
    Which scope function adds a side effect, like logging, in a chain?
    kotlin
    val user = repository.save(newUser)
        .also { log.info("saved ${it.id}") }
    also receives the object as it and returns it unchanged.
  42. 42
    What are a and b?
    kotlin
    val a = "hello".let { it.length }
    val b = "hello".also { it.length }
    a is 5, because let returns the lambda’s result; b is "hello", because also returns the receiver
  43. 43
    What is a suspend function?
    A function that can pause without blocking its thread, e.g. while waiting for I/O, and resume later. It can only be called from a coroutine or another suspend function
  44. 44
    launch vs async
    launch starts a coroutine for its side effects and returns a Job; async starts one that produces a value and returns a Deferred<T>, read with await()
  45. 45
    Fetch two things concurrently and combine them
    kotlin
    suspend fun loadPage() = coroutineScope {
        val user = async { api.fetchUser() }
        val posts = async { api.fetchPosts() }
        Page(user.await(), posts.await())
    }
  46. 46
    What is structured concurrency?
    Every coroutine runs inside a scope that waits for all its children; cancelling the scope cancels them, and a failing child cancels its siblings and parent, so nothing leaks
    A supervisorScope lets children fail independently.
  47. 47
    Which dispatcher for which work?
    Dispatchers.Main for UI, Dispatchers.IO for blocking I/O, Dispatchers.Default for CPU-heavy work. Switch with withContext
    kotlin
    val text = withContext(Dispatchers.IO) { file.readText() }
  48. 48
    delay() vs Thread.sleep()
    delay suspends the coroutine and frees the thread for other work; Thread.sleep blocks the whole thread
  49. 49
    When is runBlocking appropriate?
    Only to bridge blocking code into coroutines, such as in main or tests. It blocks the current thread, so never use it inside coroutines or on a UI thread
  50. 50
    What is a Flow?
    A cold asynchronous stream: the flow { … } block runs again for each collector, sending values with emit that are received with collect
    kotlin
    flow { for (i in 1..3) { delay(100); emit(i) } }
        .collect { println(it) }

Frequently asked questions

Which Kotlin version does the deck use?

Current Kotlin 2.x. Nearly everything also applies to late 1.x releases; newer additions like the ..< range operator (1.9) are marked.

Do I need to know Java first?

No. A few cards compare Kotlin with Java, such as static members and platform types, but they make sense on their own.

Does it cover Android or Jetpack Compose?

No. It covers the language and the kotlinx.coroutines basics that Android, Compose and server-side Kotlin all build on.

Intermediate

JavaScript

Master the JavaScript every developer uses daily: scope, closures, equality, array methods, promises, async/await and the event loop.

50 cards
Intermediate

Advanced JS

Fifty advanced JavaScript cards: event-loop ordering puzzles, generators and async iteration, this binding, Proxy and Reflect, modules, AbortController and coercion edge cases.

50 cards
Intermediate

TypeScript

Learn the TypeScript you use every day: narrowing, generics, keyof, mapped and conditional types, utility types, satisfies and strict mode, with code on every card.

50 cards
Beginner

Python

Learn idiomatic Python fast: comprehensions, f-strings, args and kwargs, generators, decorators, context managers and the standard library.

50 cards
Beginner

Git & terminal

The Git commands and shell skills developers use every day: branching, rebasing, undoing mistakes, pipes, redirects, permissions and processes.

50 cards
Beginner

Java

Core Java with short snippets: equals vs ==, records, sealed types, collections, generics, exceptions, streams, lambdas and JVM basics.

50 cards
Beginner

C#

C# and .NET fundamentals with short snippets: value vs reference types, properties, records, nullable reference types, LINQ, async/await and DI.

50 cards
Beginner

Swift

Swift fundamentals with short snippets: optionals, structs vs classes, protocols, enums with associated values, closures, ARC, async/await and SwiftUI.

50 cards
Beginner

Flutter

Flutter and Dart basics: null safety, futures and streams, stateless vs stateful widgets, setState, hot reload, keys, layout and state management.

50 cards
Beginner

PHP

Modern PHP 8 with short snippets: strict types, arrays, match, nullsafe and named arguments, enums, readonly, Composer, PDO and password_hash.

50 cards
Beginner

Ruby

Ruby fundamentals with short snippets: blocks, procs and lambdas, symbols, Enumerable, mixins, truthiness, method_missing, gems and Bundler.

50 cards
Beginner

Go

Go (Golang) fundamentals with short, idiomatic snippets: slices vs arrays, maps, defer, error wrapping, interfaces, goroutines, channels, select and context.

50 cards
Intermediate

Rust

Learn the Rust that the compiler keeps asking about: ownership, borrowing, lifetimes, Option and Result, traits, closures, smart pointers and async.

50 cards
Intermediate

C++

Modern C++ in short snippets: references vs pointers, RAII, smart pointers, move semantics, STL containers and algorithms, const, templates and UB.

50 cards
Intermediate

Haskell

Haskell fundamentals with short code: types and type classes, pattern matching, laziness, Maybe and Either, folds, Functor, Applicative and Monad.

50 cards
Intermediate

Elixir

Elixir fundamentals with short snippets: immutability, pattern matching, the pipe operator, Enum vs Stream, processes, GenServer, supervisors and mix.

50 cards
Intermediate

x86-64 assembly

x86-64 assembly in Intel syntax: registers and sub-registers, flags, mov and lea, jumps, the stack frame, the System V calling convention and Linux syscalls.

50 cards
Beginner

Regex

Learn regular expressions for good: anchors, classes, greedy vs lazy quantifiers, groups, lookarounds, flags, replacements and the backtracking trap.

50 cards