Software design · For beginners

Design Patterns in Kotlin: 50+ Patterns With Diagrams and Playground Exercises

Khairul Alam·October 2026·~2 hours with exercises
Design patternsKotlinSOLIDConcurrencyAndroid

Design patterns are named solutions to problems every programmer meets. This article follows the catalogue on Wikipedia’s Software design pattern page: 53 patterns in Kotlin across four families, each with a diagram, a short example, the SOLID principles it practises, and code you can run in the Kotlin Playground. The ones you will use most are marked ★ Popular.

The whole article in 60 seconds
  • Creational (10): how objects are made. Singleton, Factory, Builder, Dependency injection, Lazy initialization…
  • Structural (11): how objects are combined. Adapter, Decorator, Proxy, Facade, Composite, Delegation…
  • Behavioral (16): how objects cooperate. Strategy, Observer, Command, State, Iterator, Chain of responsibility…
  • Concurrency (16): many things at once. Lock, Thread pool, Scheduler, Reactor, Active object…
  • Link: patterns are SOLID in action; each one points to the matching parts of the SOLID article.
  • Rule: use a pattern to remove pain, never to look clever.
Contents
  1. What is a design pattern
  2. Catalogue at a glance
  3. Creational patterns (10)
  4. Structural patterns (11)
  5. Behavioral patterns (16)
  6. Concurrency patterns (16)
  7. Patterns and SOLID at a glance
  8. When not to use a pattern
  9. Where to go next

1. What is a design pattern?

A design pattern is a named, proven answer to a problem that keeps appearing in code. It is not a library you import. It is a shape you recognize and reuse, and the name is the real gift: “put a proxy in front of it” replaces a ten-minute explanation.

Think of recipes. “Fold the egg whites into the batter” is a technique with a name. Cooks do not reinvent it each time. Patterns are the named techniques of programming.

Where this article comes from. It is inspired mainly by Wikipedia’s Software design pattern page and follows its catalogue of Creational, Structural, Behavioral and Concurrency patterns. Of the 58 patterns listed there, this article covers 53 (about 91%). The names and families are Wikipedia’s; the explanations, Kotlin code, diagrams and exercises are my own. I left out five that do not translate to Kotlin or Android: Twin (a workaround for languages without multiple inheritance), Front controller and Service handler (server-side web and network servers), Compute kernel (GPU programming) and Safe Concurrency with Exclusive Ownership of Objects (a niche research pattern).

Patterns are SOLID in action

My SOLID principles article gives the five rules of good design. Patterns are the ready-made structures that follow those rules. Under each pattern you will find which SOLID letters it demonstrates, with links to those sections, so you can read the two side by side.

What “★ Popular Popular” means

I marked 25 of the 53 patterns as highly popular. This is my judgment, not a measured statistic: these are the ones you will meet almost every week in Android and Kotlin code (and in interviews), such as Singleton, Factory, Builder, Dependency injection, Adapter, Decorator, Proxy, Facade, Strategy, Observer, Command, State, Iterator, Lock and Thread pool. The rest are real and useful, just less frequent.

Practice is built in

Every pattern has a diagram and a Kotlin example you can open in the free Kotlin Playground (new tab, no install, no account). 15 patterns come with a full exercise: a starter with gaps, tests that print PASS or FAIL, a hint and a solution. The others are complete runnable programs: press Run, change something, run again.

Catalogue at a glance

Open a family to see its list. Inside each family the highly popular patterns (★) come first, then the rest. Tap a name to jump to it.

Creational10 patterns, 6 popular
PatternIn one linePopularPractice
Abstract factoryA factory of factories: one interface that creates a whole family of related objects, so you can swap the entire family (light theme widgets, dark theme widgets) at once.★Run it
BuilderBuild a complex object step by step, then validate and produce it in one go.★Exercise
Dependency injectionA class does not create what it needs.★Exercise
FactoryOne place decides which class to create, so the rest of the code only knows the interface.★Exercise
Lazy initializationDelay creating something expensive until the first time it is really needed, then remember it.★Run it
SingletonExactly one instance of a class, reachable from everywhere.★Exercise
MultitonLike Singleton, but one instance per key: a map of named, shared instances.Run it
Object poolKeep a set of ready-made, reusable objects and lend them out, instead of creating and destroying them again and again.Run it
PrototypeCreate new objects by copying an existing one and changing a few fields, instead of building from scratch.Run it
Resource acquisition is initialization (RAII)Tie a resource (file, connection, lock) to the lifetime of an object, so it is released automatically even when something fails.Run it
Structural11 patterns, 7 popular
PatternIn one linePopularPractice
AdapterMake an object with the wrong interface fit the interface you need, by wrapping it.★Exercise
CompositeTreat a single object and a group of objects the same way, by giving both one interface.★Run it
DecoratorAdd behavior to an object by wrapping it in another object with the same interface, instead of creating a subclass for every combination.★Exercise
DelegationAn object hands part of its work to a helper object.★Run it
FacadeOne simple entry point in front of a group of complicated classes.★Exercise
ModuleGroup related code behind one public surface and hide the rest.★Run it
ProxyA stand-in with the same interface as the real object, placed in front of it to add control: caching, lazy loading, access checks, logging.★Exercise
BridgeSplit a thing into two separate hierarchies, an abstraction and an implementation, joined by a reference, so each can vary on its own.Run it
Extension objectAdd new behavior to a type without changing it.Run it
FlyweightShare the heavy, repeated part of many objects (the same tree type, the same icon) instead of storing a copy in each one.Run it
MarkerAn interface or annotation with no methods that only tags a class, so other code can treat tagged classes specially.Run it
Behavioral16 patterns, 9 popular
PatternIn one linePopularPractice
Chain of responsibilityPass a request along a chain of handlers.★Exercise
CommandTurn an action into an object with execute() (and often undo()).★Exercise
Fluent interfaceDesign an API so calls chain into readable, sentence-like code, usually by returning the same object from each method.★Run it
IteratorWalk through a collection one item at a time without knowing how it is stored inside.★Run it
Null objectInstead of returning null and checking everywhere, return a harmless object that does nothing, so callers never need an if.★Exercise
ObserverAn object (the subject) keeps a list of listeners and tells them whenever something changes, without knowing who they are.★Exercise
StateRepresent each state as its own object that knows what may happen next, instead of one big when over a status field.★Exercise
StrategyPut each interchangeable algorithm behind one small interface and let the caller choose which one to use.★Exercise
Template methodA base class fixes the order of the steps of an algorithm; subclasses fill in some of the steps.★Exercise
BlackboardSeveral independent experts read and write a shared workspace (the blackboard).Run it
InterpreterRepresent a small language as a tree of objects and give each node an evaluate step.Run it
MediatorObjects do not talk to each other directly.Run it
MementoCapture an object’s state in a snapshot so you can restore it later (undo), without exposing its insides.Run it
ServantPut behavior that many unrelated classes need into one helper class (the servant), instead of copying it into each of them.Run it
SpecificationExpress business rules as small objects that answer yes or no, then combine them with and, or, not.Run it
VisitorAdd new operations to a family of classes without editing them, by letting a visitor object handle each class.Run it
Concurrency16 patterns, 3 popular
PatternIn one linePopularPractice
LockOnly one thread at a time may run a critical section; the others wait their turn.★Run it
SchedulerDecide when and on which thread tasks run: now, later, repeatedly, on a pool.★Run it
Thread poolKeep a fixed number of worker threads and feed them tasks from a queue, instead of creating a new thread for each task.★Run it
Active objectAn object owns its own thread and a queue.Run it
BalkingIf an object is not in the right state for a request (already busy), the request is ignored instead of waiting.Run it
Binding propertiesLink two properties so that when one changes, the other follows automatically.Run it
Double-checked lockingCheck a condition without a lock first (fast path), and only lock and check again if work is really needed.Run it
Event-based asynchronousStart work in the background and get notified by an event (callback) when it finishes or fails, instead of waiting for it.Run it
Guarded suspensionA thread waits until a condition is true (a message has arrived) before it continues.Run it
JoinWait until several concurrent tasks have all finished before continuing.Run it
Messaging design pattern (MDP)Components communicate by sending messages into a queue instead of calling each other directly or sharing memory.Run it
Monitor objectAn object whose methods are all mutually exclusive, so only one thread is inside it at a time.Run it
ReactorOne event loop waits for events and dispatches each to the handler registered for it.Run it
Read-write lockMany threads may read at the same time, but a writer needs exclusive access.Run it
Thread-specific storageGive each thread its own private copy of a value, so threads do not need locks to share it.Run it
CPU atomic operationUse a single indivisible hardware-supported step (like compare-and-set) so threads can update a value safely without locks.Run it

2. Creational patterns

How objects get made. These patterns hide the details of creation so the rest of the code does not depend on concrete classes.

Abstract factory ★ Popular

A factory of factories: one interface that creates a whole family of related objects, so you can swap the entire family (light theme widgets, dark theme widgets) at once.

Clientuses interfacesWidgetFactorybutton(), checkbox()Light or Darkmatching family
How Abstract factory fits together.

The idea in Kotlin (a complete program you can run)

interface Button { fun render(): String }
interface Checkbox { fun render(): String }
interface WidgetFactory { fun button(): Button; fun checkbox(): Checkbox }

class LightButton : Button { override fun render() = "[ light button ]" }
class LightCheckbox : Checkbox { override fun render() = "[ ] light checkbox" }
class DarkButton : Button { override fun render() = "[ dark button ]" }
class DarkCheckbox : Checkbox { override fun render() = "[x] dark checkbox" }

class LightFactory : WidgetFactory {
    override fun button() = LightButton()
    override fun checkbox() = LightCheckbox()
}
class DarkFactory : WidgetFactory {
    override fun button() = DarkButton()
    override fun checkbox() = DarkCheckbox()
}

fun screen(factory: WidgetFactory) = println(factory.button().render() + "  " + factory.checkbox().render())

fun main() {
    screen(LightFactory())
    screen(DarkFactory())      // the whole family changes together
}
Linked to SOLID

O: Open/Closed · D: Dependency Inversion

New families are added as new factories (O) and screens depend only on the factory interface (D).

In Android and Kotlin: Theme or platform kits, a DataSourceFactory that returns matching local and remote classes for each build flavor.

Run it: Abstract factory

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Builder ★ Popular

Build a complex object step by step, then validate and produce it in one go. Kotlin gives you named arguments, default values and lambda-with-receiver DSLs.

url = ...method = ...header(k, v)RequestBuildercollects, validatesRequestimmutable result
Set options in any order; build() makes the finished object.

The idea in Kotlin

data class Request(val url: String, val method: String, val headers: Map<String, String>)

class RequestBuilder {
    var url = ""
    var method = "GET"
    private val headers = mutableMapOf<String, String>()
    fun header(key: String, value: String) { headers[key] = value }
    fun build(): Request {
        require(url.isNotBlank()) { "url is required" }
        return Request(url, method, headers.toMap())
    }
}
fun request(init: RequestBuilder.() -> Unit) = RequestBuilder().apply(init).build()

val r = request { url = "https://example.com"; header("Accept", "application/json") }
Linked to SOLID

S: Single Responsibility

Construction rules live in the builder, not scattered across callers: Single Responsibility (S). The finished object stays immutable.

In Android and Kotlin: Retrofit.Builder(), AlertDialog.Builder, NotificationCompat.Builder, Room.databaseBuilder.

Try it: Builder

Fill in header, build and the request { } DSL function. build() must reject a blank url with require. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

data class Request(val url: String, val method: String, val headers: Map<String, String>)

class RequestBuilder {
    var url = ""
    var method = "GET"
    private val headers = mutableMapOf<String, String>()

    fun header(key: String, value: String) { TODO("remember the header") }
    fun build(): Request = TODO("check the url, then create the Request")
}

fun request(init: RequestBuilder.() -> Unit): Request = TODO("create a builder, run init on it, build")
Open in Kotlin Playground ↗
Hint

RequestBuilder().apply(init) runs your lambda on a new builder, then call build() on the result. headers.toMap() makes an immutable copy.

See the tests
fun main() {
    test("builds with defaults") {
        val r = request { url = "https://x.dev" }
        check(r == Request("https://x.dev", "GET", emptyMap())) { "got $r" }
    }
    test("keeps headers and method") {
        val r = request { url = "https://x.dev"; method = "POST"; header("Accept", "json") }
        check(r == Request("https://x.dev", "POST", mapOf("Accept" to "json"))) { "got $r" }
    }
    test("blank url is rejected") {
        val problem = runCatching { request { } }.exceptionOrNull()
        check(problem is IllegalArgumentException) { "expected IllegalArgumentException, got $problem" }
    }
}
Show a solution
    fun header(key: String, value: String) { headers[key] = value }
    fun build(): Request {
        require(url.isNotBlank()) { "url is required" }
        return Request(url, method, headers.toMap())
    }
}

fun request(init: RequestBuilder.() -> Unit): Request = RequestBuilder().apply(init).build()

Where the solution shows only part of the code, replace the matching part of the starter.

Dependency injection ★ Popular

A class does not create what it needs. Its dependencies are handed in from outside, usually through the constructor, so you can give it real or fake ones.

main / Hiltbuilds objectsReportServicegets Clock + SinkFake in testssame interface
How Dependency injection fits together.

The idea in Kotlin

interface Clock { fun now(): Long }
interface Sink { fun write(line: String) }

class ReportService(private val clock: Clock, private val sink: Sink) {
    fun publish(title: String) = sink.write("$title @ ${clock.now()}")
}

object SystemClock : Clock { override fun now() = System.currentTimeMillis() }
object ConsoleSink : Sink { override fun write(line: String) = println(line) }

fun main() {
    ReportService(SystemClock, ConsoleSink).publish("Daily")   // wired once, at the edge
}
Linked to SOLID

D: Dependency Inversion

This is Dependency Inversion (D) made practical: depend on Clock and Sink interfaces, receive the real ones at the edge of the app.

In Android and Kotlin: Hilt, Dagger, Koin. The architecture series covers them in Part 6.

Try it: Dependency injection

Make ReportService write "<title> @ <time>" to the sink, using only the injected clock (never System.currentTimeMillis()). Then the tests can control time. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

interface Clock { fun now(): Long }
interface Sink { fun write(line: String) }

class ReportService(private val clock: Clock, private val sink: Sink) {
    fun publish(title: String) { TODO("write \"<title> @ <time>\" to the sink using the injected clock") }
}
Open in Kotlin Playground ↗
Hint

Use string templates: "$title @ ${clock.now()}". The point is that time and output come from the constructor, so the test supplies fakes.

See the tests
class FakeClock(private val t: Long) : Clock { override fun now() = t }
class ListSink : Sink {
    val lines = mutableListOf<String>()
    override fun write(line: String) { lines.add(line) }
}

fun main() {
    test("writes title and injected time") {
        val sink = ListSink()
        ReportService(FakeClock(1000), sink).publish("Daily")
        check(sink.lines == listOf("Daily @ 1000")) { "got ${sink.lines}" }
    }
    test("a different clock gives a different line") {
        val sink = ListSink()
        ReportService(FakeClock(42), sink).publish("Weekly")
        check(sink.lines == listOf("Weekly @ 42")) { "got ${sink.lines}" }
    }
}
Show a solution
class ReportService(private val clock: Clock, private val sink: Sink) {
    fun publish(title: String) { sink.write("$title @ ${clock.now()}") }
}

Where the solution shows only part of the code, replace the matching part of the starter.

Factory ★ Popular

One place decides which class to create, so the rest of the code only knows the interface.

ClientFactorycreate(kind)EmailNotifierSmsNotifierPushNotifier
The client only knows the Notifier interface, never the concrete class.

The idea in Kotlin

interface Notifier { fun send(message: String): String }
class EmailNotifier : Notifier { override fun send(message: String) = "email:$message" }
class SmsNotifier   : Notifier { override fun send(message: String) = "sms:$message" }

object NotifierFactory {
    fun create(kind: String): Notifier = when (kind) {
        "email" -> EmailNotifier()
        "sms"   -> SmsNotifier()
        else    -> throw IllegalArgumentException("unknown kind: $kind")
    }
}
Linked to SOLID

O: Open/Closed · D: Dependency Inversion

The client depends on the Notifier interface (D), and adding a new kind touches only the factory (O at the construction point).

In Android and Kotlin: ViewModelProvider.Factory, Fragment factories, creating the right repository for a build flavor.

Try it: Factory

Finish NotifierFactory.create so it returns the right notifier for "email", "sms" and "push", and throws IllegalArgumentException for anything else. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

interface Notifier { fun send(message: String): String }
class EmailNotifier : Notifier { override fun send(message: String) = "email:$message" }
class SmsNotifier   : Notifier { override fun send(message: String) = "sms:$message" }
class PushNotifier  : Notifier { override fun send(message: String) = "push:$message" }

object NotifierFactory {
    fun create(kind: String): Notifier = TODO("pick the class for this kind")
}
Open in Kotlin Playground ↗
Hint

Use a when (kind) expression with one branch per kind, and an else that throws.

See the tests
fun main() {
    test("email") { check(NotifierFactory.create("email").send("hi") == "email:hi") { "wrong notifier" } }
    test("sms")   { check(NotifierFactory.create("sms").send("hi") == "sms:hi") { "wrong notifier" } }
    test("push")  { check(NotifierFactory.create("push").send("hi") == "push:hi") { "wrong notifier" } }
    test("unknown kind is rejected") {
        val problem = runCatching { NotifierFactory.create("fax") }.exceptionOrNull()
        check(problem is IllegalArgumentException) { "expected IllegalArgumentException, got $problem" }
    }
}
Show a solution
object NotifierFactory {
    fun create(kind: String): Notifier = when (kind) {
        "email" -> EmailNotifier()
        "sms"   -> SmsNotifier()
        "push"  -> PushNotifier()
        else    -> throw IllegalArgumentException("unknown kind: $kind")
    }
}

Where the solution shows only part of the code, replace the matching part of the starter.

Lazy initialization ★ Popular

Delay creating something expensive until the first time it is really needed, then remember it.

First accesscreate itStored valuerememberedNext accessesreuse
How Lazy initialization fits together.

The idea in Kotlin (a complete program you can run)

class Heavy {
    init { println("Heavy created") }
    fun hello() = "hello"
}

class Screen {
    val heavy by lazy { Heavy() }      // not created yet
}

fun main() {
    val screen = Screen()
    println("screen created")
    println(screen.heavy.hello())      // created here, on first use
    println(screen.heavy.hello())      // reused
}
Linked to SOLID

S: Single Responsibility · D: Dependency Inversion

Expensive setup lives behind the property (S); callers just use the value.

In Android and Kotlin: Kotlin has it built in: by lazy { }, plus lateinit var for values set later.

Run it: Lazy initialization

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Singleton ★ Popular

Exactly one instance of a class, reachable from everywhere. In Kotlin you write object and the language does the rest.

Caller ACaller BCaller CIdGeneratorobject: one instanceone counter
Every caller reaches the same single instance.

The idea in Kotlin

object Logger {                       // one instance, created on first use
    fun log(msg: String) = println("[LOG] $msg")
}

Logger.log("started")               // no constructor, no getInstance()

// Better for testing: depend on an interface, make ONE implementation by DI
interface Clock { fun now(): Long }
object SystemClock : Clock { override fun now() = System.currentTimeMillis() }
Linked to SOLID

D: Dependency Inversion · S: Single Responsibility

Singleton is the pattern to be careful with. A hidden global breaks Dependency Inversion (D): code that calls IdGenerator.next() directly cannot be given a fake in tests. Prefer to inject it through an interface and let DI (Hilt, Koin) make it single.

In Android and Kotlin: Application-scoped objects such as a database or an HTTP client (@Singleton in Hilt).

Try it: Singleton

Implement next() so ids count 1, 2, 3 across the whole program, and reset() so tests can start clean. Notice that you needed reset() at all: that is the cost of global state. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

object IdGenerator {
    private var last = 0
    fun next(): Int = TODO("return 1, then 2, then 3, ...")
    fun reset() { TODO("start counting from the beginning again") }
}
Open in Kotlin Playground ↗
Hint

++last increases the number and returns the new value. reset() sets it back to 0.

See the tests
fun main() {
    test("counts up from 1") {
        IdGenerator.reset()
        check(IdGenerator.next() == 1) { "first id should be 1" }
        check(IdGenerator.next() == 2) { "second id should be 2" }
    }
    test("every caller shares one counter") {
        IdGenerator.reset()
        val fromA = IdGenerator.next()
        val fromB = IdGenerator.next()
        check(fromB == fromA + 1) { "callers do not share one counter" }
    }
    test("reset starts again") {
        IdGenerator.next(); IdGenerator.next()
        IdGenerator.reset()
        check(IdGenerator.next() == 1) { "reset did not restart the count" }
    }
}
Show a solution
object IdGenerator {
    private var last = 0
    fun next(): Int = ++last
    fun reset() { last = 0 }
}

Where the solution shows only part of the code, replace the matching part of the starter.

Multiton

Like Singleton, but one instance per key: a map of named, shared instances.

Logger.of("net")keyRegistry mapname to instanceSame Loggerfor same key
How Multiton fits together.

The idea in Kotlin (a complete program you can run)

class Logger private constructor(val name: String) {
    companion object {
        private val instances = mutableMapOf<String, Logger>()
        fun of(name: String): Logger = instances.getOrPut(name) { Logger(name) }
    }
    fun log(message: String) = println("[$name] $message")
}

fun main() {
    Logger.of("net").log("connected")
    println(Logger.of("net") === Logger.of("net"))   // true: same key, same instance
    println(Logger.of("net") === Logger.of("db"))    // false: different key
}
Linked to SOLID

D: Dependency Inversion

Same caution as Singleton: shared global state is hard to fake in tests, so hide it behind an interface (D).

In Android and Kotlin: A logger per tag, one DataStore per file name, one database per user.

Run it: Multiton

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Object pool

Keep a set of ready-made, reusable objects and lend them out, instead of creating and destroying them again and again.

Borrowtake a free oneUsedo the workReturnback to the pool
How Object pool fits together.

The idea in Kotlin (a complete program you can run)

class Pool<T>(size: Int, create: () -> T) {
    private val free = ArrayDeque<T>().apply { repeat(size) { addLast(create()) } }

    fun <R> use(block: (T) -> R): R {
        val item = free.removeFirstOrNull() ?: error("pool exhausted")
        try {
            return block(item)
        } finally {
            free.addLast(item)         // always give it back
        }
    }
}

fun main() {
    var created = 0
    val pool = Pool(2) { created++; StringBuilder() }
    repeat(5) { i ->
        pool.use { sb ->
            sb.setLength(0)
            sb.append("job ").append(i)
            println(sb)
        }
    }
    println("objects created: $created")   // 2, not 5
}
Linked to SOLID

S: Single Responsibility · D: Dependency Inversion

The pool owns reuse (S); users borrow through a small interface.

In Android and Kotlin: RecyclerView view holders and its RecycledViewPool, bitmap pools in image loaders, database connection pools, Message.obtain().

Run it: Object pool

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Prototype

Create new objects by copying an existing one and changing a few fields, instead of building from scratch.

Base objectEnemy("Goblin")copy(...)change a few fieldsNew objectGoblin Chief
How Prototype fits together.

The idea in Kotlin (a complete program you can run)

data class Enemy(val name: String, val hp: Int, val tags: List<String>)

fun main() {
    val goblin = Enemy("Goblin", 10, listOf("green", "small"))
    val chief = goblin.copy(name = "Goblin Chief", hp = 30)

    println(goblin)
    println(chief)
    println(goblin.tags === chief.tags)   // true: the list itself is shared (shallow copy)
}
Linked to SOLID

O: Open/Closed

New variants appear by copying, with no new class needed (O).

In Android and Kotlin: Kotlin data class gives you copy() for free. Remember it is a shallow copy.

Run it: Prototype

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Resource acquisition is initialization (RAII)

Tie a resource (file, connection, lock) to the lifetime of an object, so it is released automatically even when something fails.

Openacquireuse { }workclose()always runs
How Resource acquisition is initialization (RAII) fits together.

The idea in Kotlin (a complete program you can run)

class Connection(val name: String) : AutoCloseable {
    init { println("open $name") }
    fun query() = "result from $name"
    override fun close() = println("close $name")
}

fun main() {
    Connection("db").use { println(it.query()) }          // closed after the block

    runCatching { Connection("cache").use { error("boom") } }
        .onFailure { println("caught: ${it.message}") }    // still closed, even on error
}
Linked to SOLID

S: Single Responsibility

Cleanup is the resource’s job, not scattered across callers (S).

In Android and Kotlin: Kotlin’s use { } on Closeable/AutoCloseable, lock.withLock { }, Room transactions.

Run it: Resource acquisition is initialization (RAII)

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

3. Structural patterns

How objects are put together. These patterns combine classes and objects into larger structures while keeping them flexible.

Adapter ★ Popular

Make an object with the wrong interface fit the interface you need, by wrapping it. Also called Wrapper or Translator.

ClientSensorAdapterThermometerLegacySensorreadFahrenheit()celsius()converts Fahrenheit
The adapter translates between two interfaces that do not match.

The idea in Kotlin

interface Thermometer { fun celsius(): Double }
class LegacySensor(private val fahrenheit: Double) { fun readFahrenheit() = fahrenheit }

class SensorAdapter(private val legacy: LegacySensor) : Thermometer {
    override fun celsius() = (legacy.readFahrenheit() - 32) * 5 / 9
}
Linked to SOLID

D: Dependency Inversion · O: Open/Closed

Your code depends on Thermometer (D); the old sensor is reused without being edited (O).

In Android and Kotlin: RecyclerView.Adapter, mappers from API DTOs to domain models, wrapping a third-party SDK.

Try it: Adapter

Write SensorAdapter.celsius() using the formula (f - 32) * 5 / 9. Do not change LegacySensor. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

interface Thermometer { fun celsius(): Double }

// Imagine this class comes from an old library. Do NOT edit it.
class LegacySensor(private val fahrenheit: Double) {
    fun readFahrenheit(): Double = fahrenheit
}

class SensorAdapter(private val legacy: LegacySensor) : Thermometer {
    override fun celsius(): Double = TODO("convert Fahrenheit to Celsius")
}
Open in Kotlin Playground ↗
Hint

Call legacy.readFahrenheit() and apply the formula. The adapter implements the interface you want and holds the thing you have.

See the tests
fun main() {
    test("boiling point") { check(same(SensorAdapter(LegacySensor(212.0)).celsius(), 100.0)) { "212F should be 100C" } }
    test("freezing point") { check(same(SensorAdapter(LegacySensor(32.0)).celsius(), 0.0)) { "32F should be 0C" } }
    test("usable as a Thermometer") {
        val t: Thermometer = SensorAdapter(LegacySensor(50.0))
        check(same(t.celsius(), 10.0)) { "50F should be 10C" }
    }
}
Show a solution
class SensorAdapter(private val legacy: LegacySensor) : Thermometer {
    override fun celsius(): Double = (legacy.readFahrenheit() - 32) * 5 / 9
}

Where the solution shows only part of the code, replace the matching part of the starter.

Composite ★ Popular

Treat a single object and a group of objects the same way, by giving both one interface. Groups contain items or other groups, forming a tree.

FileNodesize()FileleafFoldercontains FileNodes
How Composite fits together.

The idea in Kotlin (a complete program you can run)

interface FileNode { val name: String; fun size(): Int }

class FileLeaf(override val name: String, private val bytes: Int) : FileNode {
    override fun size() = bytes
}

class Folder(override val name: String, private val children: List<FileNode>) : FileNode {
    override fun size() = children.sumOf { it.size() }      // works for files AND folders
}

fun main() {
    val tree = Folder("root", listOf(
        FileLeaf("a.txt", 10),
        Folder("img", listOf(FileLeaf("b.png", 200), FileLeaf("c.png", 300)))
    ))
    println("total size = ${tree.size()}")                  // 510
}
Linked to SOLID

O: Open/Closed · L: Liskov Substitution

Folders and files are interchangeable FileNodes (L); you can add node types without touching callers (O).

In Android and Kotlin: Android View and ViewGroup, the Jetpack Compose tree, menus with submenus, file trees.

Run it: Composite

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Decorator ★ Popular

Add behavior to an object by wrapping it in another object with the same interface, instead of creating a subclass for every combination.

Milk +0.5Sugar +0.2Espresso 2.0
Each wrapper adds to the inner object and still looks like a Coffee.

The idea in Kotlin

interface Coffee { fun cost(): Double; fun description(): String }
class Espresso : Coffee { override fun cost() = 2.0; override fun description() = "espresso" }

class Milk(private val inner: Coffee) : Coffee {
    override fun cost() = inner.cost() + 0.5
    override fun description() = inner.description() + " + milk"
}
val order: Coffee = Milk(Espresso())
Linked to SOLID

O: Open/Closed · L: Liskov Substitution · S: Single Responsibility

New features arrive as new wrappers, never as edits (O). Every wrapper can stand in for the original (L), and each does one small job (S).

In Android and Kotlin: OkHttp interceptors, ContextWrapper, a repository wrapped with logging or caching.

Try it: Decorator

Complete Milk (+0.5) and Sugar (+0.2). Each must wrap another Coffee and extend its cost and description. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

interface Coffee { fun cost(): Double; fun description(): String }

class Espresso : Coffee {
    override fun cost(): Double = 2.0
    override fun description(): String = "espresso"
}

class Milk(private val inner: Coffee) : Coffee {
    override fun cost(): Double = TODO("inner cost plus 0.5")
    override fun description(): String = TODO("inner description followed by  + milk")
}

class Sugar(private val inner: Coffee) : Coffee {
    override fun cost(): Double = TODO("inner cost plus 0.2")
    override fun description(): String = TODO("inner description followed by  + sugar")
}
Open in Kotlin Playground ↗
Hint

Always start from inner.cost() and inner.description(), then add your part.

See the tests
fun main() {
    test("plain espresso") {
        check(same(Espresso().cost(), 2.0)) { "wrong cost" }
    }
    test("milk adds cost and text") {
        val c = Milk(Espresso())
        check(same(c.cost(), 2.5)) { "wrong cost ${c.cost()}" }
        check(c.description() == "espresso + milk") { "wrong text '${c.description()}'" }
    }
    test("wrappers stack in any order") {
        val c = Milk(Sugar(Espresso()))
        check(same(c.cost(), 2.7)) { "wrong cost ${c.cost()}" }
        check(c.description() == "espresso + sugar + milk") { "wrong text '${c.description()}'" }
    }
}
Show a solution
class Milk(private val inner: Coffee) : Coffee {
    override fun cost(): Double = inner.cost() + 0.5
    override fun description(): String = inner.description() + " + milk"
}

class Sugar(private val inner: Coffee) : Coffee {
    override fun cost(): Double = inner.cost() + 0.2
    override fun description(): String = inner.description() + " + sugar"
}

Where the solution shows only part of the code, replace the matching part of the starter.

Delegation ★ Popular

An object hands part of its work to a helper object. Kotlin supports this in the language with the by keyword.

Reportimplements Printerby printerforwards callsConsolePrinterdoes the work
How Delegation fits together.

The idea in Kotlin (a complete program you can run)

interface Printer { fun print(text: String) }
class ConsolePrinter : Printer { override fun print(text: String) = println(text) }

class Report(printer: Printer) : Printer by printer           // every call is forwarded

class TimestampPrinter(private val inner: Printer) : Printer by inner {
    override fun print(text: String) = inner.print("[12:00] $text")   // override only what changes
}

fun main() {
    Report(ConsolePrinter()).print("plain")
    TimestampPrinter(ConsolePrinter()).print("stamped")
}
Linked to SOLID

O: Open/Closed · S: Single Responsibility · D: Dependency Inversion

Prefer composition over inheritance: reuse behavior by delegating (O) to an interface (D), without a fragile base class.

In Android and Kotlin: class Foo : Bar by impl, property delegates (by lazy, by viewModels(), by Delegates.observable).

Run it: Delegation

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Facade ★ Popular

One simple entry point in front of a group of complicated classes. Callers make one call; the facade coordinates the rest.

Callerorder(item)OrderFacadeone methodInventory, Payment, Shippinghidden
How Facade fits together.

The idea in Kotlin

class Inventory { fun reserve(item: String) = item != "ghost" }
class Payment { fun charge(amount: Double) = amount > 0 }
class Shipping { fun schedule(item: String) = "ship:$item" }

class OrderFacade(private val inventory: Inventory, private val payment: Payment, private val shipping: Shipping) {
    fun order(item: String, amount: Double): String {
        if (!inventory.reserve(item)) return "failed"
        if (!payment.charge(amount)) return "failed"
        return shipping.schedule(item)
    }
}
Linked to SOLID

S: Single Responsibility · I: Interface Segregation

Callers depend on one small interface (I), and the coordination lives in one place (S).

In Android and Kotlin: A repository hiding a database and an API, a UseCase hiding several repositories, an SDK wrapper such as AnalyticsManager.

Try it: Facade

Complete OrderFacade.order: reserve the item, charge the amount, then schedule shipping and return its code. If a step fails, return "failed" and do not continue. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

class Inventory { fun reserve(item: String): Boolean = item != "ghost" }
class Payment { fun charge(amount: Double): Boolean = amount > 0 }
class Shipping { fun schedule(item: String): String = "ship:$item" }

class OrderFacade(
    private val inventory: Inventory,
    private val payment: Payment,
    private val shipping: Shipping,
) {
    fun order(item: String, amount: Double): String =
        TODO("reserve, charge, then schedule shipping; return the shipping code or \"failed\"")
}
Open in Kotlin Playground ↗
Hint

Use early returns: stop at the first failing step so shipping is never scheduled for an unpaid order.

See the tests
fun main() {
    val facade = OrderFacade(Inventory(), Payment(), Shipping())
    test("successful order returns the shipping code") {
        check(facade.order("pen", 2.0) == "ship:pen") { "wrong result" }
    }
    test("unknown item fails") {
        check(facade.order("ghost", 2.0) == "failed") { "expected failed" }
    }
    test("payment failure fails") {
        check(facade.order("pen", 0.0) == "failed") { "expected failed" }
    }
}
Show a solution
    fun order(item: String, amount: Double): String {
        if (!inventory.reserve(item)) return "failed"
        if (!payment.charge(amount)) return "failed"
        return shipping.schedule(item)
    }

Where the solution shows only part of the code, replace the matching part of the starter.

Module ★ Popular

Group related code behind one public surface and hide the rest. Callers see a small API, not the internals.

Public APIfew functionsMathModuleone objectPrivate detailshidden
How Module fits together.

The idea in Kotlin (a complete program you can run)

object MathModule {
    private const val TWO_PI = 6.283185307179586      // hidden detail
    fun circumference(radius: Double) = TWO_PI * radius
    fun area(radius: Double) = Math.PI * radius * radius
}

fun main() {
    println(MathModule.circumference(1.0))
    println(MathModule.area(2.0))
    // MathModule.TWO_PI would not compile: it is private
}
Linked to SOLID

S: Single Responsibility · I: Interface Segregation · D: Dependency Inversion

One module, one purpose (S), a small public API (I), and others depend on the API, not the insides (D).

In Android and Kotlin: Kotlin object, internal and private visibility, Gradle modules (see architecture Part 5).

Run it: Module

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Proxy ★ Popular

A stand-in with the same interface as the real object, placed in front of it to add control: caching, lazy loading, access checks, logging.

ClientCachingLoadersame interfaceNetworkLoaderslow, real workanswers from cache when it canonly on a miss
Same interface in front, so the client cannot tell the difference. ViewProxy and GlideProxy work this way.

The idea in Kotlin

interface ImageLoader { fun load(url: String): String }
class GlideProxy : ImageLoader {                       // hides the library
    override fun load(url: String) = "Glide loaded $url"
}
class LoggingLoader(private val real: ImageLoader) : ImageLoader {
    override fun load(url: String): String {
        println("loading $url")
        return real.load(url)
    }
}
Linked to SOLID

L: Liskov Substitution · D: Dependency Inversion

A proxy must behave like the real thing (L), and callers depend only on the interface (D). This is the pattern behind ViewProxy and GlideProxy in my architecture series.

In Android and Kotlin: ViewProxy, GlideProxy (wrapping Glide behind an ImageLoader), by lazy, Retrofit’s generated service.

Try it: Proxy

Make CachingLoader ask the real loader only the first time a url is requested, and return the saved result afterwards. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

interface ImageLoader { fun load(url: String): String }

class NetworkLoader : ImageLoader {
    var calls = 0
    override fun load(url: String): String { calls++; return "bytes of $url" }
}

class CachingLoader(private val real: ImageLoader) : ImageLoader {
    private val cache = mutableMapOf<String, String>()
    override fun load(url: String): String = TODO("use the cache, call real only on a miss")
}
Open in Kotlin Playground ↗
Hint

getOrPut(key) { value } returns the stored value, or computes it, stores it and returns it.

See the tests
fun main() {
    test("returns the real result") {
        val proxy = CachingLoader(NetworkLoader())
        check(proxy.load("a.png") == "bytes of a.png") { "wrong result" }
    }
    test("asks the real loader once per url") {
        val real = NetworkLoader()
        val proxy = CachingLoader(real)
        proxy.load("a.png"); proxy.load("a.png"); proxy.load("a.png")
        check(real.calls == 1) { "real loader was called ${real.calls} times" }
    }
    test("different urls are cached separately") {
        val real = NetworkLoader()
        val proxy = CachingLoader(real)
        proxy.load("a.png"); proxy.load("b.png"); proxy.load("a.png")
        check(real.calls == 2) { "real loader was called ${real.calls} times" }
    }
}
Show a solution
class CachingLoader(private val real: ImageLoader) : ImageLoader {
    private val cache = mutableMapOf<String, String>()
    override fun load(url: String): String = cache.getOrPut(url) { real.load(url) }
}

Where the solution shows only part of the code, replace the matching part of the starter.

Bridge

Split a thing into two separate hierarchies, an abstraction and an implementation, joined by a reference, so each can vary on its own.

Shapewhat to drawRendererhow to drawVector or Rasterimplementations
How Bridge fits together.

The idea in Kotlin (a complete program you can run)

interface Renderer { fun drawCircle(radius: Int): String }
class VectorRenderer : Renderer { override fun drawCircle(radius: Int) = "vector circle r=$radius" }
class RasterRenderer : Renderer { override fun drawCircle(radius: Int) = "raster circle r=$radius px" }

abstract class Shape(protected val renderer: Renderer) { abstract fun draw(): String }
class Circle(private val radius: Int, renderer: Renderer) : Shape(renderer) {
    override fun draw() = renderer.drawCircle(radius)
}

fun main() {
    println(Circle(5, VectorRenderer()).draw())
    println(Circle(5, RasterRenderer()).draw())   // new renderer, no new Shape class
}
Linked to SOLID

O: Open/Closed · D: Dependency Inversion · S: Single Responsibility

Shapes and renderers change independently (O, S) and meet through an interface (D).

In Android and Kotlin: A View drawing through a Canvas, a repository using any DataSource, UI code with a swappable Logger.

Run it: Bridge

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Extension object

Add new behavior to a type without changing it. In Kotlin the natural tool is the extension function.

Existing typeUser, Stringfun User.fullName()added from outsideCall siteu.fullName()
How Extension object fits together.

The idea in Kotlin (a complete program you can run)

data class User(val first: String, val last: String)

fun User.fullName() = "$first $last"                          // User is unchanged
fun String.initials() = split(" ").joinToString("") { it.take(1) }

fun main() {
    val user = User("Ada", "Lovelace")
    println(user.fullName())
    println(user.fullName().initials())
}
Linked to SOLID

O: Open/Closed

Add behavior from outside, leaving the original class untouched (O).

In Android and Kotlin: fun String.toSlug(), View.show(), Android KTX (core-ktx) is a big collection of extensions.

Run it: Extension object

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Flyweight

Share the heavy, repeated part of many objects (the same tree type, the same icon) instead of storing a copy in each one.

1000 Treesx, y onlyTreeTypes cacheshared2 TreeType objectsoak, pine
How Flyweight fits together.

The idea in Kotlin (a complete program you can run)

data class TreeType(val name: String, val color: String)   // imagine big textures here

object TreeTypes {
    private val cache = mutableMapOf<String, TreeType>()
    fun get(name: String, color: String) = cache.getOrPut(name + color) { TreeType(name, color) }
}

class Tree(val x: Int, val y: Int, val type: TreeType)

fun main() {
    val forest = (1..1000).map {
        Tree(it, it * 2, TreeTypes.get(if (it % 2 == 0) "oak" else "pine", "green"))
    }
    println("trees: ${forest.size}")
    println("distinct tree types: ${forest.map { it.type }.distinct().size}")   // 2
}
Linked to SOLID

S: Single Responsibility

Shared data is separated from per-object data (S).

In Android and Kotlin: String interning, bitmap caches, text styles shared by many spans, Integer.valueOf cache.

Run it: Flyweight

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Marker

An interface or annotation with no methods that only tags a class, so other code can treat tagged classes specially.

interface Sensitiveno methodsPasswordis Sensitivelog(item)checks the tag
How Marker fits together.

The idea in Kotlin (a complete program you can run)

interface Sensitive                       // marker: nothing to implement

data class Password(val value: String) : Sensitive
data class City(val name: String)

fun log(item: Any) = println(if (item is Sensitive) "<hidden>" else item.toString())

fun main() {
    log(City("Tokyo"))
    log(Password("hunter2"))
}
Linked to SOLID

O: Open/Closed

Add a tag instead of editing the code that reacts to it (O).

In Android and Kotlin: Serializable, RandomAccess, custom annotations such as @Composable or @VisibleForTesting.

Run it: Marker

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

4. Behavioral patterns

How objects talk to each other and share work. These patterns assign responsibilities and manage communication.

Chain of responsibility ★ Popular

Pass a request along a chain of handlers. Each one either handles it or passes it to the next.

Team leadup to 100Managerup to 1000Directorup to 10000
How Chain of responsibility fits together.

The idea in Kotlin

abstract class Handler(private val next: Handler?) {
    fun handle(request: String): String = if (canHandle(request)) name() else next?.handle(request) ?: "unhandled"
    protected abstract fun canHandle(request: String): Boolean
    protected abstract fun name(): String
}
class Auth(next: Handler?) : Handler(next) {
    override fun canHandle(request: String) = request == "login"
    override fun name() = "auth"
}
class Logging : Handler(null) {
    override fun canHandle(request: String) = true
    override fun name() = "logging"
}
Linked to SOLID

O: Open/Closed · S: Single Responsibility · L: Liskov Substitution

Handlers are added or reordered without editing others (O), and each decides only its own case (S).

In Android and Kotlin: OkHttp interceptors, touch event dispatch through views, try/catch ladders, middleware, validation steps.

Try it: Chain of responsibility

Complete Approver.approve: approve at this level (return its title) when the amount is within its limit; otherwise pass the request to next. If nobody is left, return "rejected". Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

abstract class Approver(private val next: Approver?) {
    abstract val limit: Int
    abstract val title: String

    fun approve(amount: Int): String =
        TODO("return title if amount <= limit, else ask next, else \"rejected\"")
}

class TeamLead(next: Approver?) : Approver(next) { override val limit = 100;   override val title = "lead" }
class Manager(next: Approver?)  : Approver(next) { override val limit = 1000;  override val title = "manager" }
class Director : Approver(null)                  { override val limit = 10000; override val title = "director" }
Open in Kotlin Playground ↗
Hint

next?.approve(amount) ?: "rejected" asks the next handler if there is one, and falls back to "rejected" at the end of the chain.

See the tests
fun main() {
    val chain = TeamLead(Manager(Director()))
    test("small amount is handled by the lead") { check(chain.approve(50) == "lead") { "got ${chain.approve(50)}" } }
    test("medium amount goes to the manager")   { check(chain.approve(500) == "manager") { "got ${chain.approve(500)}" } }
    test("large amount goes to the director")   { check(chain.approve(5000) == "director") { "got ${chain.approve(5000)}" } }
    test("too large is rejected")               { check(chain.approve(50000) == "rejected") { "got ${chain.approve(50000)}" } }
}
Show a solution
    fun approve(amount: Int): String =
        if (amount <= limit) title else next?.approve(amount) ?: "rejected"

Where the solution shows only part of the code, replace the matching part of the starter.

Command ★ Popular

Turn an action into an object with execute() (and often undo()). You can then queue it, log it, retry it or undo it.

ButtoninvokerCommandexecute / undoEditorreceiver: textHistory stackundo() pops the last command
Because actions are objects, you can queue, log and undo them.

The idea in Kotlin

interface Command { fun execute(); fun undo() }
class Editor { var text = "" }

class Append(private val editor: Editor, private val extra: String) : Command {
    override fun execute() { editor.text += extra }
    override fun undo() { editor.text = editor.text.removeSuffix(extra) }
}
Linked to SOLID

S: Single Responsibility · O: Open/Closed · D: Dependency Inversion

The invoker does not know what the action does (D); each command is one small class (S); new commands are added without edits (O).

In Android and Kotlin: Undo snackbars, WorkManager work requests, Runnable/Handler.post, MVI intents/actions (Part 4 of the architecture series).

Try it: Command

Finish History: run executes a command and remembers it, undo reverses the most recent one (and does nothing when there is nothing to undo). Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

class Editor { var text = "" }

interface Command { fun execute(); fun undo() }

class Append(private val editor: Editor, private val extra: String) : Command {
    override fun execute() { editor.text += extra }
    override fun undo() { editor.text = editor.text.removeSuffix(extra) }
}

class History {
    private val done = ArrayDeque<Command>()
    fun run(command: Command) { TODO("execute it and remember it") }
    fun undo() { TODO("undo the most recent command, or do nothing if there is none") }
}
Open in Kotlin Playground ↗
Hint

removeLastOrNull() returns null on an empty list, and ?.undo() then does nothing.

See the tests
fun main() {
    test("run applies commands in order") {
        val editor = Editor(); val history = History()
        history.run(Append(editor, "Hello"))
        history.run(Append(editor, " world"))
        check(editor.text == "Hello world") { "got '${editor.text}'" }
    }
    test("undo reverses the latest command") {
        val editor = Editor(); val history = History()
        history.run(Append(editor, "Hello"))
        history.run(Append(editor, " world"))
        history.undo()
        check(editor.text == "Hello") { "got '${editor.text}'" }
        history.undo()
        check(editor.text == "") { "got '${editor.text}'" }
    }
    test("undo with nothing to undo is safe") {
        History().undo()
    }
}
Show a solution
class History {
    private val done = ArrayDeque<Command>()
    fun run(command: Command) {
        command.execute()
        done.addLast(command)
    }
    fun undo() {
        done.removeLastOrNull()?.undo()
    }
}

Where the solution shows only part of the code, replace the matching part of the starter.

Fluent interface ★ Popular

Design an API so calls chain into readable, sentence-like code, usually by returning the same object from each method.

Query()start.from().where()chain.limit()finish
How Fluent interface fits together.

The idea in Kotlin (a complete program you can run)

class Query {
    private val parts = mutableListOf<String>()
    fun from(table: String) = apply { parts += "FROM $table" }
    fun where(condition: String) = apply { parts += "WHERE $condition" }
    fun limit(n: Int) = apply { parts += "LIMIT $n" }
    override fun toString() = "SELECT * " + parts.joinToString(" ")
}

fun main() {
    println(Query().from("users").where("age > 18").limit(10))
}
Linked to SOLID

S: Single Responsibility · I: Interface Segregation

Each method does one small step (S) and the chain exposes only what is needed (I).

In Android and Kotlin: Builders, Retrofit.Builder().baseUrl().build(), Kotlin apply, collection chains such as list.filter{}.map{}, Room and query DSLs.

Run it: Fluent interface

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Iterator ★ Popular

Walk through a collection one item at a time without knowing how it is stored inside.

CountdownIterableiterator()hasNext / nextfor (n in ...)loop
How Iterator fits together.

The idea in Kotlin (a complete program you can run)

class Countdown(private val from: Int) : Iterable<Int> {
    override fun iterator() = object : Iterator<Int> {
        private var current = from
        override fun hasNext() = current > 0
        override fun next() = current--
    }
}

fun main() {
    for (n in Countdown(3)) println(n)                     // 3 2 1
    println(Countdown(5).filter { it % 2 == 1 })           // works with all collection helpers
}
Linked to SOLID

S: Single Responsibility · L: Liskov Substitution

Traversal is separate from storage (S) and any Iterable can be used the same way (L).

In Android and Kotlin: Every for loop, Iterator, Sequence, Cursor from a database, Flow.

Run it: Iterator

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Null object ★ Popular

Instead of returning null and checking everywhere, return a harmless object that does nothing, so callers never need an if.

forUser(id)never nullReal DiscountorNoDiscountdoes nothing
How Null object fits together.

The idea in Kotlin

interface Logger { fun log(message: String) }
class ConsoleLogger : Logger { override fun log(message: String) = println(message) }
object NoLogger : Logger { override fun log(message: String) {} }     // the null object

class Service(private val logger: Logger = NoLogger) {
    fun work(): Int { logger.log("working"); return 42 }               // no null checks
}
Linked to SOLID

L: Liskov Substitution · D: Dependency Inversion

The do-nothing object is a valid substitute for the real one (L), so callers depend only on the interface (D).

In Android and Kotlin: Kotlin’s null safety covers many cases, but emptyList(), a no-op Logger or Analytics, and Collections.emptyIterator() are null objects.

Try it: Null object

Make NoDiscount return the price unchanged, and make DiscountBook.forUser return the user’s discount, or NoDiscount when there is none (never null). Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

interface Discount { fun apply(price: Double): Double }

class Percent(private val pct: Double) : Discount {
    override fun apply(price: Double): Double = price * (1 - pct / 100)
}

object NoDiscount : Discount {
    override fun apply(price: Double): Double = TODO("return the price unchanged")
}

class DiscountBook(private val byUser: Map<String, Discount>) {
    fun forUser(id: String): Discount = TODO("the user's discount, or NoDiscount")
}
Open in Kotlin Playground ↗
Hint

The elvis operator ?: gives you a fallback when the map lookup is null.

See the tests
fun main() {
    val book = DiscountBook(mapOf("ana" to Percent(10.0)))
    test("known user gets a discount") { check(same(book.forUser("ana").apply(100.0), 90.0)) { "wrong price" } }
    test("unknown user pays full price") { check(same(book.forUser("zed").apply(100.0), 100.0)) { "wrong price" } }
    test("no discount leaves the price alone") { check(same(NoDiscount.apply(55.5), 55.5)) { "wrong price" } }
}
Show a solution
object NoDiscount : Discount {
    override fun apply(price: Double): Double = price
}

class DiscountBook(private val byUser: Map<String, Discount>) {
    fun forUser(id: String): Discount = byUser[id] ?: NoDiscount
}

Where the solution shows only part of the code, replace the matching part of the starter.

Observer ★ Popular

An object (the subject) keeps a list of listeners and tells them whenever something changes, without knowing who they are. It is also known as publish/subscribe.

Subjectemit(value)Observer: screenObserver: loggerObserver: analytics
The subject does not know who listens. This is StateFlow, LiveData and click listeners.

The idea in Kotlin

class Subject<T> {
    private val listeners = mutableListOf<(T) -> Unit>()
    fun subscribe(l: (T) -> Unit): () -> Unit { listeners.add(l); return { listeners.remove(l) } }
    fun emit(value: T) = listeners.toList().forEach { it(value) }
}
val s = Subject<String>()
val stop = s.subscribe { println("got $it") }
s.emit("hello")
stop()                         // unsubscribe, so nothing leaks
Linked to SOLID

D: Dependency Inversion · O: Open/Closed

The subject depends only on a listener type (D), and you add new listeners without changing the subject (O).

In Android and Kotlin: StateFlow, LiveData, Rx Observable, OnClickListener, broadcast receivers.

Try it: Observer

Write subscribe (it returns a function that unsubscribes) and emit. Forgetting to unsubscribe is a classic Android memory leak, so test that unsubscribing works. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

class Subject<T> {
    private val listeners = mutableListOf<(T) -> Unit>()

    fun subscribe(listener: (T) -> Unit): () -> Unit = TODO("add it, and return a function that removes it")
    fun emit(value: T) { TODO("tell every listener") }
}
Open in Kotlin Playground ↗
Hint

Loop over a copy (listeners.toList()) so a listener that unsubscribes during emit does not break the loop.

See the tests
fun main() {
    test("every subscriber receives the value") {
        val subject = Subject<String>()
        val a = mutableListOf<String>()
        val b = mutableListOf<String>()
        subject.subscribe { a.add(it) }
        subject.subscribe { b.add(it) }
        subject.emit("hi")
        check(a == listOf("hi") && b == listOf("hi")) { "a=$a b=$b" }
    }
    test("unsubscribe stops delivery") {
        val subject = Subject<Int>()
        val seen = mutableListOf<Int>()
        val stop = subject.subscribe { seen.add(it) }
        subject.emit(1)
        stop()
        subject.emit(2)
        check(seen == listOf(1)) { "got $seen" }
    }
    test("a listener may unsubscribe while being notified") {
        val subject = Subject<Int>()
        var stop: () -> Unit = {}
        stop = subject.subscribe { stop() }
        subject.emit(1)
    }
}
Show a solution
class Subject<T> {
    private val listeners = mutableListOf<(T) -> Unit>()

    fun subscribe(listener: (T) -> Unit): () -> Unit {
        listeners.add(listener)
        return { listeners.remove(listener) }
    }
    fun emit(value: T) { listeners.toList().forEach { it(value) } }
}

Where the solution shows only part of the code, replace the matching part of the starter.

State ★ Popular

Represent each state as its own object that knows what may happen next, instead of one big when over a status field.

CreatedPaidShippedDeliveredCancelledcancel() is only allowed in Created and Paid
Each state class decides what is allowed next, replacing a giant when.

The idea in Kotlin

sealed interface UiState {
    object Loading : UiState
    data class Content(val items: List<String>) : UiState
    data class Error(val message: String) : UiState
}
// the view reacts to the current state; no scattered boolean flags like isLoading && !hasError
Linked to SOLID

O: Open/Closed · S: Single Responsibility

Each state class has one job (S) and a new state is a new class (O). Illegal moves fail in one clear place.

In Android and Kotlin: Screen state in MVVM (Loading, Content, Error sealed types), media player states, order or download status.

Try it: State

Finish the order lifecycle. Created and Paid can move on and be cancelled; Shipped can only move to Delivered. Any other move must throw IllegalStateException (the interface already does this by default). Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

sealed interface OrderState {
    fun next(): OrderState = throw IllegalStateException("no next state from ${this::class.simpleName}")
    fun cancel(): OrderState = throw IllegalStateException("cannot cancel from ${this::class.simpleName}")
}

object Created : OrderState {
    override fun next(): OrderState = TODO("go to Paid")
    override fun cancel(): OrderState = TODO("go to Cancelled")
}

object Paid : OrderState {
    override fun next(): OrderState = TODO("go to Shipped")
    override fun cancel(): OrderState = TODO("go to Cancelled")
}

object Shipped : OrderState {
    override fun next(): OrderState = TODO("go to Delivered")
}

object Delivered : OrderState
object Cancelled : OrderState
Open in Kotlin Playground ↗
Hint

Return the next object. States that do not override a method inherit the default, which throws. That is exactly the behavior you want.

See the tests
fun main() {
    test("happy path") {
        check(Created.next() === Paid) { "Created -> Paid" }
        check(Paid.next() === Shipped) { "Paid -> Shipped" }
        check(Shipped.next() === Delivered) { "Shipped -> Delivered" }
    }
    test("can cancel before shipping") {
        check(Created.cancel() === Cancelled) { "Created -> Cancelled" }
        check(Paid.cancel() === Cancelled) { "Paid -> Cancelled" }
    }
    test("cannot cancel after shipping") {
        val problem = runCatching { Shipped.cancel() }.exceptionOrNull()
        check(problem is IllegalStateException) { "expected IllegalStateException, got $problem" }
    }
    test("delivered is final") {
        val problem = runCatching { Delivered.next() }.exceptionOrNull()
        check(problem is IllegalStateException) { "expected IllegalStateException, got $problem" }
    }
}
Show a solution
object Created : OrderState {
    override fun next(): OrderState = Paid
    override fun cancel(): OrderState = Cancelled
}

object Paid : OrderState {
    override fun next(): OrderState = Shipped
    override fun cancel(): OrderState = Cancelled
}

object Shipped : OrderState {
    override fun next(): OrderState = Delivered
}

Where the solution shows only part of the code, replace the matching part of the starter.

Strategy ★ Popular

Put each interchangeable algorithm behind one small interface and let the caller choose which one to use.

OrdercontextShippingCostinterfaceStandardExpress
Swap the strategy object to swap the algorithm.

The idea in Kotlin

interface ShippingCost { fun cost(kg: Double): Double }
class Standard : ShippingCost { override fun cost(kg: Double) = 5.0 }
class Express  : ShippingCost { override fun cost(kg: Double) = 10.0 + 2.0 * kg }

class Order(private var shipping: ShippingCost) {
    fun shippingFor(kg: Double) = shipping.cost(kg)
}
Linked to SOLID

O: Open/Closed · D: Dependency Inversion · I: Interface Segregation

Replaces a growing when with new classes: Open/Closed (O). The context depends on the interface (D), kept small (I).

In Android and Kotlin: DispatcherProvider / schedulers, sort orders, payment providers, Comparator.

Try it: Strategy

Implement Express (10 plus 2 per kilogram) and let an Order switch strategy at run time with switchTo. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

interface ShippingCost { fun cost(kg: Double): Double }

class Standard : ShippingCost {
    override fun cost(kg: Double): Double = 5.0
}

class Express : ShippingCost {
    override fun cost(kg: Double): Double = TODO("10 plus 2 per kilogram")
}

class Order(private var shipping: ShippingCost) {
    fun switchTo(newShipping: ShippingCost) { TODO("use the new strategy from now on") }
    fun shippingFor(kg: Double): Double = shipping.cost(kg)
}
Open in Kotlin Playground ↗
Hint

switchTo only has to replace the stored strategy: shipping = newShipping.

See the tests
fun main() {
    test("standard is flat") { check(same(Order(Standard()).shippingFor(3.0), 5.0)) { "wrong cost" } }
    test("express depends on weight") { check(same(Express().cost(3.0), 16.0)) { "wrong cost" } }
    test("order can switch strategy") {
        val order = Order(Standard())
        order.switchTo(Express())
        check(same(order.shippingFor(3.0), 16.0)) { "still using the old strategy" }
    }
}
Show a solution
class Express : ShippingCost {
    override fun cost(kg: Double): Double = 10.0 + 2.0 * kg
}

class Order(private var shipping: ShippingCost) {
    fun switchTo(newShipping: ShippingCost) { shipping = newShipping }
    fun shippingFor(kg: Double): Double = shipping.cost(kg)
}

Where the solution shows only part of the code, replace the matching part of the starter.

Template method ★ Popular

A base class fixes the order of the steps of an algorithm; subclasses fill in some of the steps.

export()fixed orderheader, body, footerstepsCsv or Htmlfill in steps
How Template method fits together.

The idea in Kotlin

abstract class Game {
    fun play() { start(); turn(); end() }          // the template: fixed order
    protected abstract fun start()
    protected abstract fun turn()
    protected open fun end() = println("game over")
}
class Chess : Game() {
    override fun start() = println("set up board")
    override fun turn() = println("move a piece")
}
Linked to SOLID

O: Open/Closed · L: Liskov Substitution

The skeleton is closed for change and variations are added by subclassing (O); subclasses stay interchangeable (L).

In Android and Kotlin: Activity.onCreate/onStart/onResume (lifecycle skeleton), AsyncTask (old), RecyclerView.Adapter, test setUp/tearDown.

Try it: Template method

Write export() so it runs header(), body() and footer() in that order, joined with a newline. Do not change the subclasses. Open the starter in the Kotlin Playground, press Run, edit the code, and run again until every line says PASS.

abstract class DataExporter {
    fun export(): String = TODO("header, body, footer joined with a newline")

    protected abstract fun header(): String
    protected abstract fun body(): String
    protected open fun footer(): String = "-- end --"
}

class CsvExporter : DataExporter() {
    override fun header() = "id,name"
    override fun body() = "1,Ana"
}

class HtmlExporter : DataExporter() {
    override fun header() = "<h1>Users</h1>"
    override fun body() = "<p>Ana</p>"
    override fun footer() = "<hr>"
}
Open in Kotlin Playground ↗
Hint

listOf(a, b, c).joinToString("\n") joins the three steps with a newline.

See the tests
fun main() {
    test("csv uses the default footer") {
        val out = CsvExporter().export()
        check(out == "id,name\n1,Ana\n-- end --") { "got '$out'" }
    }
    test("html overrides the footer") {
        val out = HtmlExporter().export()
        check(out == "<h1>Users</h1>\n<p>Ana</p>\n<hr>") { "got '$out'" }
    }
}
Show a solution
    fun export(): String = listOf(header(), body(), footer()).joinToString("\n")

Where the solution shows only part of the code, replace the matching part of the starter.

Blackboard

Several independent experts read and write a shared workspace (the blackboard). Each acts when the data it needs appears, until the problem is solved.

Blackboardshared factsExpertstokenize, countResultfacts grow
How Blackboard fits together.

The idea in Kotlin (a complete program you can run)

class Blackboard { val facts = mutableMapOf<String, Any>() }

interface Expert {
    fun canContribute(b: Blackboard): Boolean
    fun contribute(b: Blackboard)
}

class Tokenizer : Expert {
    override fun canContribute(b: Blackboard) = "text" in b.facts && "words" !in b.facts
    override fun contribute(b: Blackboard) { b.facts["words"] = (b.facts["text"] as String).split(" ") }
}

class Counter : Expert {
    override fun canContribute(b: Blackboard) = "words" in b.facts && "count" !in b.facts
    override fun contribute(b: Blackboard) { b.facts["count"] = (b.facts["words"] as List<*>).size }
}

fun main() {
    val board = Blackboard().apply { facts["text"] = "design patterns in kotlin" }
    val experts = listOf(Counter(), Tokenizer())          // order does not matter
    while (true) {
        val expert = experts.firstOrNull { it.canContribute(board) } ?: break
        expert.contribute(board)
    }
    println(board.facts)
}
Linked to SOLID

O: Open/Closed · S: Single Responsibility · D: Dependency Inversion

New experts plug in without changing the others (O); each has one skill (S).

In Android and Kotlin: Pipelines where steps react to shared state, speech and image recognition, shared build or sync context.

Run it: Blackboard

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Interpreter

Represent a small language as a tree of objects and give each node an evaluate step. Good for rules, formulas and filters.

Add(2, Mul(3, 4))treeeval(expr)walk the tree14result
How Interpreter fits together.

The idea in Kotlin (a complete program you can run)

sealed interface Expr
data class Num(val value: Int) : Expr
data class Add(val left: Expr, val right: Expr) : Expr
data class Mul(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int = when (e) {
    is Num -> e.value
    is Add -> eval(e.left) + eval(e.right)
    is Mul -> eval(e.left) * eval(e.right)
}

fun main() {
    println(eval(Add(Num(2), Mul(Num(3), Num(4)))))   // 2 + 3 * 4 = 14
}
Linked to SOLID

O: Open/Closed

A new kind of expression is a new class or branch (O).

In Android and Kotlin: Regex engines, SQL parsers, search filters, simple rule engines. A Kotlin sealed interface plus when is the compact form.

Run it: Interpreter

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Mediator

Objects do not talk to each other directly. They talk through a mediator that knows who should hear what, which untangles many-to-many wiring.

Ana, Ben, CycolleaguesChatRoommediatorMessagesrouted
How Mediator fits together.

The idea in Kotlin (a complete program you can run)

interface Mediator { fun send(from: String, message: String) }

class User(val name: String) {
    var room: Mediator? = null
    fun say(message: String) { room?.send(name, message) }
    fun receive(message: String) = println("$name got '$message'")
}

class ChatRoom : Mediator {
    private val users = mutableMapOf<String, User>()
    fun join(user: User) { users[user.name] = user; user.room = this }
    override fun send(from: String, message: String) {
        users.values.filter { it.name != from }.forEach { it.receive("$from: $message") }
    }
}

fun main() {
    val room = ChatRoom()
    val ana = User("Ana"); val ben = User("Ben"); val cy = User("Cy")
    listOf(ana, ben, cy).forEach { room.join(it) }
    ana.say("hi everyone")          // Ben and Cy hear it; nobody holds a reference to anyone else
}
Linked to SOLID

S: Single Responsibility · D: Dependency Inversion

Coordination lives in one place (S), participants depend on the mediator interface (D).

In Android and Kotlin: A chat room, a form controller that enables Submit when fields are valid, Dialog/ViewModel coordinating screens, an event bus.

Run it: Mediator

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Memento

Capture an object’s state in a snapshot so you can restore it later (undo), without exposing its insides.

Editortext = "v1"save()Memento("v1")restore(m)back to v1
How Memento fits together.

The idea in Kotlin (a complete program you can run)

class Editor {
    var text = ""
    class Memento(val text: String)

    fun save() = Memento(text)
    fun restore(m: Memento) { text = m.text }
}

fun main() {
    val editor = Editor()
    editor.text = "version 1"
    val snapshot = editor.save()
    editor.text = "version 2 (oops)"
    println(editor.text)
    editor.restore(snapshot)
    println(editor.text)                 // version 1
}
Linked to SOLID

S: Single Responsibility

The snapshot is its own small type (S); the owner decides what to save.

In Android and Kotlin: onSaveInstanceState and SavedStateHandle, undo stacks, game save points.

Run it: Memento

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Servant

Put behavior that many unrelated classes need into one helper class (the servant), instead of copying it into each of them.

Sprite, Labelare MovableMoveServantmoveBy()Positions changeboth updated
How Servant fits together.

The idea in Kotlin (a complete program you can run)

interface Movable { var x: Int; var y: Int }
class Sprite(override var x: Int, override var y: Int) : Movable
class Label(override var x: Int, override var y: Int) : Movable

object MoveServant {
    fun moveBy(items: List<Movable>, dx: Int, dy: Int) {
        items.forEach { it.x += dx; it.y += dy }
    }
}

fun main() {
    val sprite = Sprite(0, 0)
    val label = Label(5, 5)
    MoveServant.moveBy(listOf(sprite, label), 2, 3)
    println("${sprite.x},${sprite.y}  ${label.x},${label.y}")
}
Linked to SOLID

S: Single Responsibility · D: Dependency Inversion

Shared behavior has one home (S); classes only need to satisfy a small interface (D).

In Android and Kotlin: Utility objects such as a Mover or Formatter that work on anything implementing a small interface.

Run it: Servant

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Specification

Express business rules as small objects that answer yes or no, then combine them with and, or, not.

cheapprice < 50availablein stockcheap and availablea deal
How Specification fits together.

The idea in Kotlin (a complete program you can run)

fun interface Spec<T> { fun isSatisfiedBy(item: T): Boolean }

infix fun <T> Spec<T>.and(other: Spec<T>) = Spec<T> { isSatisfiedBy(it) && other.isSatisfiedBy(it) }
infix fun <T> Spec<T>.or(other: Spec<T>) = Spec<T> { isSatisfiedBy(it) || other.isSatisfiedBy(it) }
operator fun <T> Spec<T>.not() = Spec<T> { !isSatisfiedBy(it) }

data class Product(val price: Int, val inStock: Boolean)

fun main() {
    val cheap = Spec<Product> { it.price < 50 }
    val available = Spec<Product> { it.inStock }
    val deal = cheap and available

    listOf(Product(20, true), Product(20, false), Product(90, true))
        .forEach { println("$it -> ${deal.isSatisfiedBy(it)}") }
    println((!cheap).isSatisfiedBy(Product(90, true)))
}
Linked to SOLID

O: Open/Closed · S: Single Responsibility · D: Dependency Inversion

New rules are new specs (O), each with one condition (S), combined through one interface (D).

In Android and Kotlin: Search filters, validation rules, feature-flag conditions, Room/SQL query builders.

Run it: Specification

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Visitor

Add new operations to a family of classes without editing them, by letting a visitor object handle each class.

Circle, Rectaccept(visitor)Visitorvisit(Circle), visit(Rect)Area or Namenew operations
How Visitor fits together.

The idea in Kotlin (a complete program you can run)

sealed interface Shape
data class Circle(val r: Double) : Shape
data class Rect(val w: Double, val h: Double) : Shape

interface ShapeVisitor<R> {
    fun visit(c: Circle): R
    fun visit(r: Rect): R
}

fun <R> Shape.accept(v: ShapeVisitor<R>): R = when (this) {
    is Circle -> v.visit(this)
    is Rect -> v.visit(this)
}

class AreaVisitor : ShapeVisitor<Double> {
    override fun visit(c: Circle) = Math.PI * c.r * c.r
    override fun visit(r: Rect) = r.w * r.h
}
class NameVisitor : ShapeVisitor<String> {
    override fun visit(c: Circle) = "circle"
    override fun visit(r: Rect) = "rectangle"
}

fun main() {
    val shapes = listOf(Circle(1.0), Rect(2.0, 3.0))
    shapes.forEach { println("${it.accept(NameVisitor())}: ${it.accept(AreaVisitor())}") }
}
Linked to SOLID

O: Open/Closed · S: Single Responsibility

New operations are new visitors (O); each visitor is one job (S).

In Android and Kotlin: In Kotlin a sealed hierarchy with when usually replaces it, but the classic form still appears in compilers, Kotlin symbol processing and AST tools.

Run it: Visitor

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

5. Concurrency patterns

How code behaves when many things run at once: threads, locks, queues and schedulers. All demos here use plain JVM classes so they run in the Kotlin Playground. In real Android apps, coroutines wrap most of these ideas.

Lock ★ Popular

Only one thread at a time may run a critical section; the others wait their turn. This prevents lost updates on shared data.

4 threadsdeposit(1) x1000lockone at a timebalance4000
How Lock fits together.

The idea in Kotlin (a complete program you can run)

import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.thread
import kotlin.concurrent.withLock

class Account {
    private val lock = ReentrantLock()
    var balance = 0
        private set

    fun deposit(n: Int) = lock.withLock { balance += n }
}

fun main() {
    val account = Account()
    val threads = List(4) { thread { repeat(1000) { account.deposit(1) } } }
    threads.forEach { it.join() }
    println(account.balance)               // always 4000; remove the lock and it may be less
}
Linked to SOLID

S: Single Responsibility

Locking sits inside the class that owns the data (S), so callers cannot forget it.

In Android and Kotlin: synchronized, ReentrantLock and withLock { }, Mutex in coroutines.

Run it: Lock

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Scheduler ★ Popular

Decide when and on which thread tasks run: now, later, repeatedly, on a pool. Keep that choice in one replaceable place.

Taskwhat to runSchedulerwhen and whereThreadruns it
How Scheduler fits together.

The idea in Kotlin (a complete program you can run)

import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

fun main() {
    val scheduler = Executors.newScheduledThreadPool(1)

    val job = scheduler.scheduleAtFixedRate({ println("tick") }, 0, 20, TimeUnit.MILLISECONDS)
    scheduler.schedule({ println("one-off, 30 ms later") }, 30, TimeUnit.MILLISECONDS)

    Thread.sleep(70)
    job.cancel(false)
    scheduler.shutdown()
}
Linked to SOLID

O: Open/Closed · D: Dependency Inversion

Code depends on a scheduler interface (D), so production uses real threads and tests use immediate ones. See AssessmentSchedulers in architecture Part 2.

In Android and Kotlin: Dispatchers.IO/Main, RxJava Schedulers, Handler.postDelayed, WorkManager, ScheduledExecutorService.

Run it: Scheduler

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Thread pool ★ Popular

Keep a fixed number of worker threads and feed them tasks from a queue, instead of creating a new thread for each task.

Many taskssubmittedQueuewaiting2 worker threadsreused
How Thread pool fits together.

The idea in Kotlin (a complete program you can run)

import java.util.concurrent.Callable
import java.util.concurrent.Executors

fun main() {
    val pool = Executors.newFixedThreadPool(2)

    val futures = (1..6).map { n -> pool.submit(Callable { n * n to Thread.currentThread().name }) }
    val results = futures.map { it.get() }

    println(results.map { it.first })                             // [1, 4, 9, 16, 25, 36]
    println("threads used: ${results.map { it.second }.distinct().size} (at most 2)")
    pool.shutdown()
}
Linked to SOLID

S: Single Responsibility · D: Dependency Inversion

Thread management is separate from the tasks (S); tasks depend on an executor interface (D).

In Android and Kotlin: Executors.newFixedThreadPool, OkHttp’s dispatcher, Dispatchers.IO (a shared pool), AsyncTask’s old thread pool.

Run it: Thread pool

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Active object

An object owns its own thread and a queue. Callers send requests and get a future back; the object handles them one at a time, so its state needs no locks.

Callersincrement()Queueone at a timeOwn threadowns the state
How Active object fits together.

The idea in Kotlin (a complete program you can run)

import java.util.concurrent.Callable
import java.util.concurrent.Executors
import java.util.concurrent.Future

class Counter {
    private val executor = Executors.newSingleThreadExecutor()
    private var value = 0                          // only ever touched by the executor thread

    fun increment(): Future<Int> = executor.submit(Callable { ++value })
    fun shutdown() = executor.shutdown()
}

fun main() {
    val counter = Counter()
    val results = (1..5).map { counter.increment() }
    println(results.map { it.get() })              // [1, 2, 3, 4, 5], in order, no locks
    counter.shutdown()
}
Linked to SOLID

S: Single Responsibility · D: Dependency Inversion

The object’s state is owned by one thread (S); callers only see a small method interface (D).

In Android and Kotlin: A single-thread executor, a HandlerThread, a coroutine actor, the Android main thread itself.

Run it: Active object

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Balking

If an object is not in the right state for a request (already busy), the request is ignored instead of waiting.

save()requestbusy?checkYes: balkreturn at once
How Balking fits together.

The idea in Kotlin (a complete program you can run)

import java.util.concurrent.atomic.AtomicBoolean

class Saver {
    private val busy = AtomicBoolean(false)

    fun save(work: () -> String = { "saved" }): String {
        if (!busy.compareAndSet(false, true)) return "balked: already saving"
        try {
            return work()
        } finally {
            busy.set(false)
        }
    }
}

fun main() {
    val saver = Saver()
    println(saver.save())                   // saved
    println(saver.save { saver.save() })    // the inner call arrives while busy, so it balks
}
Linked to SOLID

S: Single Responsibility

The state check sits inside the object (S).

In Android and Kotlin: Ignoring a second tap while a save is running, debouncing a refresh, if (isLoading) return.

Run it: Balking

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Binding properties

Link two properties so that when one changes, the other follows automatically.

Property aset("hello")bindlistenersProperty bfollows
How Binding properties fits together.

The idea in Kotlin (a complete program you can run)

class Property<T>(initial: T) {
    private var value = initial
    private val listeners = mutableListOf<(T) -> Unit>()

    fun get() = value
    fun set(newValue: T) {
        if (newValue != value) {               // only fire on a real change, so bindings cannot loop
            value = newValue
            listeners.forEach { it(newValue) }
        }
    }
    fun bind(other: Property<T>) {
        other.set(value)
        listeners.add { other.set(it) }
        other.listeners.add { set(it) }
    }
}

fun main() {
    val a = Property("x")
    val b = Property("y")
    a.bind(b)
    println(b.get())          // x
    a.set("hello")
    println(b.get())          // hello
    b.set("from b")
    println(a.get())          // from b
}
Linked to SOLID

O: Open/Closed · D: Dependency Inversion

Views and models stay in sync without knowing each other (D), and new bindings need no edits (O).

In Android and Kotlin: Jetpack Compose State, StateFlow collected into a UI, Data Binding two-way @={}, JavaFX properties.

Run it: Binding properties

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Double-checked locking

Check a condition without a lock first (fast path), and only lock and check again if work is really needed. Used to create a shared object once.

instance != null?fast pathsynchronizedonly if nullcheck againthen create
How Double-checked locking fits together.

The idea in Kotlin (a complete program you can run)

class Config private constructor() {
    companion object {
        @Volatile private var instance: Config? = null

        fun get(): Config =
            instance ?: synchronized(this) {            // 1st check without a lock
                instance ?: Config().also { instance = it }   // 2nd check inside the lock
            }
    }
}

fun main() {
    println(Config.get() === Config.get())     // true
    // In real code, prefer:  val config by lazy { Config() }
}
Linked to SOLID

S: Single Responsibility

Thread-safe creation is isolated in one place (S).

In Android and Kotlin: Kotlin hides it: by lazy uses it internally. Write it yourself only to learn why @Volatile matters.

Run it: Double-checked locking

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Event-based asynchronous

Start work in the background and get notified by an event (callback) when it finishes or fails, instead of waiting for it.

fetchAsync()returns at onceBackground threadworksonDone / onErrorevent
How Event-based asynchronous fits together.

The idea in Kotlin (a complete program you can run)

import kotlin.concurrent.thread

fun fetchAsync(id: Int, onDone: (String) -> Unit, onError: (Throwable) -> Unit): Thread = thread {
    try {
        Thread.sleep(50)                    // pretend to call a server
        onDone("user#$id")
    } catch (e: Exception) {
        onError(e)
    }
}

fun main() {
    val worker = fetchAsync(7, onDone = { println("done: $it") }, onError = { println("error: $it") })
    println("main keeps going")             // printed first
    worker.join()
}
Linked to SOLID

O: Open/Closed · D: Dependency Inversion

The caller depends on callback types, not on how the work is done (D).

In Android and Kotlin: Click and response listeners, Call.enqueue(callback) in Retrofit, broadcast receivers. Coroutines and Futures are the modern replacement.

Run it: Event-based asynchronous

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Guarded suspension

A thread waits until a condition is true (a message has arrived) before it continues.

Readertake(): waitsMailboxconditionWriterput(): signals
How Guarded suspension fits together.

The idea in Kotlin (a complete program you can run)

import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.thread
import kotlin.concurrent.withLock

class Mailbox {
    private val lock = ReentrantLock()
    private val hasMessage = lock.newCondition()
    private var message: String? = null

    fun put(m: String) = lock.withLock {
        message = m
        hasMessage.signalAll()
    }

    fun take(): String = lock.withLock {
        while (message == null) hasMessage.await()     // the guard: wait until it is true
        message!!.also { message = null }
    }
}

fun main() {
    val box = Mailbox()
    val reader = thread { println("got: ${box.take()}") }
    Thread.sleep(50)                                   // the reader is waiting
    box.put("hello")
    reader.join()
}
Linked to SOLID

S: Single Responsibility

Waiting rules are encapsulated in the mailbox (S).

In Android and Kotlin: BlockingQueue.take(), CountDownLatch, a coroutine channel.receive() (suspends instead of blocking).

Run it: Guarded suspension

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Join

Wait until several concurrent tasks have all finished before continuing.

3 workersrun in paralleljoin()wait for eachCombineall results
How Join fits together.

The idea in Kotlin (a complete program you can run)

import kotlin.concurrent.thread

fun main() {
    val results = java.util.Collections.synchronizedList(mutableListOf<Int>())

    val workers = (1..3).map { n ->
        thread {
            Thread.sleep(10L * n)             // different durations
            results.add(n * n)
        }
    }
    workers.forEach { it.join() }             // wait for all of them
    println(results.sorted())                 // [1, 4, 9]
}
Linked to SOLID

S: Single Responsibility

The waiting logic is in one place (S).

In Android and Kotlin: Thread.join(), CountDownLatch, awaitAll() on coroutine Deferreds, Tasks.whenAll, Rx zip.

Run it: Join

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Messaging design pattern (MDP)

Components communicate by sending messages into a queue instead of calling each other directly or sharing memory.

Producerput(msg)QueueinboxConsumertake(msg)
How Messaging design pattern (MDP) fits together.

The idea in Kotlin (a complete program you can run)

import java.util.concurrent.LinkedBlockingQueue
import kotlin.concurrent.thread

sealed interface Msg
data class Greet(val name: String) : Msg
object Stop : Msg

fun main() {
    val inbox = LinkedBlockingQueue<Msg>()

    val consumer = thread {
        while (true) {
            val msg = inbox.take()               // waits for the next message
            if (msg is Greet) println("hello ${msg.name}") else break
        }
    }

    inbox.put(Greet("Ana"))
    inbox.put(Greet("Ben"))
    inbox.put(Stop)
    consumer.join()
}
Linked to SOLID

O: Open/Closed · S: Single Responsibility · D: Dependency Inversion

Producers and consumers are decoupled (D); new message types are added without editing the sender (O).

In Android and Kotlin: Handler/Looper, Channel, BlockingQueue, WorkManager, Kafka and other brokers on the backend.

Run it: Messaging design pattern (MDP)

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Monitor object

An object whose methods are all mutually exclusive, so only one thread is inside it at a time. The object guards its own state.

Thread 1, 2, 3call methodsMonitorone at a timevalue <= maxinvariant holds
How Monitor object fits together.

The idea in Kotlin (a complete program you can run)

import kotlin.concurrent.thread

class BoundedCounter(private val max: Int) {
    private var value = 0

    @Synchronized fun increment(): Boolean {
        if (value >= max) return false
        value++
        return true
    }
    @Synchronized fun current() = value
}

fun main() {
    val counter = BoundedCounter(5)
    val threads = List(4) { thread { repeat(10) { counter.increment() } } }
    threads.forEach { it.join() }
    println(counter.current())            // exactly 5, never more
}
Linked to SOLID

S: Single Responsibility

The class protects its own invariants (S).

In Android and Kotlin: Java/Kotlin @Synchronized methods, Vector, StringBuffer.

Run it: Monitor object

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Reactor

One event loop waits for events and dispatches each to the handler registered for it.

post(type, data)events inReactor loopdispatchHandlersby type
How Reactor fits together.

The idea in Kotlin (a complete program you can run)

class Reactor {
    private val handlers = mutableMapOf<String, (String) -> Unit>()
    private val events = ArrayDeque<Pair<String, String>>()

    fun register(type: String, handler: (String) -> Unit) { handlers[type] = handler }
    fun post(type: String, payload: String) { events.addLast(type to payload) }

    fun run() {
        while (events.isNotEmpty()) {
            val (type, payload) = events.removeFirst()
            val handler = handlers[type]
            if (handler != null) handler(payload) else println("no handler for $type")
        }
    }
}

fun main() {
    val reactor = Reactor()
    reactor.register("click") { println("clicked $it") }
    reactor.register("key") { println("key $it") }
    reactor.post("click", "OK")
    reactor.post("key", "A")
    reactor.post("scroll", "10")
    reactor.run()
}
Linked to SOLID

O: Open/Closed · D: Dependency Inversion

Handlers register for event types without the loop knowing them (O, D).

In Android and Kotlin: The Android main thread’s Looper + Handler, Netty and other event-loop servers, Node.js.

Run it: Reactor

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Read-write lock

Many threads may read at the same time, but a writer needs exclusive access. Faster than one plain lock when reads dominate.

Readersin parallelLockread / writeOne writeralone
How Read-write lock fits together.

The idea in Kotlin (a complete program you can run)

import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write

class Cache {
    private val lock = ReentrantReadWriteLock()
    private val map = mutableMapOf<String, String>()

    fun get(key: String): String? = lock.read { map[key] }          // many readers allowed
    fun put(key: String, value: String) { lock.write { map[key] = value } }   // exclusive
}

fun main() {
    val cache = Cache()
    cache.put("lang", "Kotlin")
    println(cache.get("lang"))
}
Linked to SOLID

S: Single Responsibility

Locking strategy is hidden inside the cache (S).

In Android and Kotlin: ReentrantReadWriteLock with Kotlin’s read { } and write { } helpers, in-memory caches.

Run it: Read-write lock

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

Thread-specific storage

Give each thread its own private copy of a value, so threads do not need locks to share it.

Thread Areq-1ThreadLocalone slot eachThread Breq-2
How Thread-specific storage fits together.

The idea in Kotlin (a complete program you can run)

import kotlin.concurrent.thread

val requestId = ThreadLocal<String>()

fun log(message: String) = println("[${requestId.get()}] $message")

fun main() {
    val a = thread { requestId.set("req-1"); log("working") }
    a.join()
    val b = thread { requestId.set("req-2"); log("working") }
    b.join()                               // each thread saw only its own value
}
Linked to SOLID

S: Single Responsibility

Per-thread context is kept out of method signatures (S).

In Android and Kotlin: ThreadLocal, the current Looper (Looper.myLooper()), coroutine context elements.

Run it: Thread-specific storage

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

CPU atomic operation

Use a single indivisible hardware-supported step (like compare-and-set) so threads can update a value safely without locks.

4 threadsincrementAndGet()Atomic CPU stepindivisible4000no lost updates
How CPU atomic operation fits together.

The idea in Kotlin (a complete program you can run)

import java.util.concurrent.atomic.AtomicInteger
import kotlin.concurrent.thread

fun main() {
    val hits = AtomicInteger()
    val threads = List(4) { thread { repeat(1000) { hits.incrementAndGet() } } }
    threads.forEach { it.join() }
    println(hits.get())                                  // always 4000

    val flag = AtomicInteger(0)
    println(flag.compareAndSet(0, 1))                    // true: it was 0, now 1
    println(flag.compareAndSet(0, 2))                    // false: it is no longer 0
}
Linked to SOLID

S: Single Responsibility

Simple shared counters stay simple (S) without a lock object.

In Android and Kotlin: AtomicInteger, AtomicReference, compareAndSet, kotlinx.atomicfu.

Run it: CPU atomic operation

Open the program in the Kotlin Playground, press Run, then change something (a value, a class, a rule) and run it again to see what happens.

Open in Kotlin Playground ↗

6. Patterns and SOLID at a glance

A tick means the pattern is a good way to practice that principle. Column letters link to the matching section of the SOLID article.

PatternSOLID
Abstract factory✓✓
Builder✓
Dependency injection✓
Factory✓✓
Lazy initialization✓✓
Singleton✓✓
Multiton✓
Object pool✓✓
Prototype✓
Resource acquisition is initialization (RAII)✓
Adapter✓✓
Composite✓✓
Decorator✓✓✓
Delegation✓✓✓
Facade✓✓
Module✓✓✓
Proxy✓✓
Bridge✓✓✓
Extension object✓
Flyweight✓
Marker✓
Chain of responsibility✓✓✓
Command✓✓✓
Fluent interface✓✓
Iterator✓✓
Null object✓✓
Observer✓✓
State✓✓
Strategy✓✓✓
Template method✓✓
Blackboard✓✓✓
Interpreter✓
Mediator✓✓
Memento✓
Servant✓✓
Specification✓✓✓
Visitor✓✓
Lock✓
Scheduler✓✓
Thread pool✓✓
Active object✓✓
Balking✓
Binding properties✓✓
Double-checked locking✓
Event-based asynchronous✓✓
Guarded suspension✓
Join✓
Messaging design pattern (MDP)✓✓✓
Monitor object✓
Reactor✓✓
Read-write lock✓
Thread-specific storage✓
CPU atomic operation✓

Notice that D, O and S show up most. Depending on small interfaces, adding behavior by adding classes, and giving each class one job are the heart of almost every pattern.

7. When not to use a pattern

8. Where to go next

← Back to all writing