Author: Wayan

  • How do I transform nested collections using flatMap in Kotlin?

    In Kotlin, flatMap is used to transform each element into a collection and then flatten the results into a single list.

    Basic idea

    val result = items.flatMap { item ->
        // return a collection for each item
    }
    

    It is similar to:

    items.map { ... }.flatten()
    

    but more concise.


    Example: flatten nested lists

    val nested = listOf(
        listOf(1, 2),
        listOf(3, 4),
        listOf(5)
    )
    
    val flattened = nested.flatMap { it }
    
    println(flattened)
    

    Output:

    [1, 2, 3, 4, 5]
    

    Transform while flattening

    val words = listOf("hi", "cat", "dog")
    
    val letters = words.flatMap { word ->
        word.toList()
    }
    
    println(letters)
    

    Output:

    [h, i, c, a, t, d, o, g]
    

    Each word becomes a list of characters, and flatMap combines them into one list.


    Example with objects

    data class Department(
        val name: String,
        val employees: List<String>
    )
    
    val departments = listOf(
        Department("Engineering", listOf("Alice", "Bob")),
        Department("HR", listOf("Carol"))
    )
    
    val allEmployees = departments.flatMap { department ->
        department.employees
    }
    
    println(allEmployees)
    

    Output:

    [Alice, Bob, Carol]
    

    Transform nested values

    data class Order(
        val id: Int,
        val items: List<String>
    )
    
    val orders = listOf(
        Order(1, listOf("Book", "Pen")),
        Order(2, listOf("Laptop"))
    )
    
    val itemDescriptions = orders.flatMap { order ->
        order.items.map { item ->
            "Order ${order.id}: $item"
        }
    }
    
    println(itemDescriptions)
    

    Output:

    [Order 1: Book, Order 1: Pen, Order 2: Laptop]
    

    Here:

    1. Each Order is transformed into a List<String>
    2. flatMap flattens all those lists into one List<String>

    map vs flatMap

    Using map:

    val result = orders.map { order ->
        order.items
    }
    

    Result type:

    List<List<String>>
    

    Using flatMap:

    val result = orders.flatMap { order ->
        order.items
    }
    

    Result type:

    List<String>
    

    Rule of thumb

    Use:

    map
    

    when each input becomes one output.

    Use:

    flatMap
    

    when each input becomes many outputs, and you want a single flattened result.

    val users = listOf(
        User("Alice", listOf("admin", "editor")),
        User("Bob", listOf("viewer"))
    )
    
    val roles = users.flatMap { it.roles }
    

    Result:

    [admin, editor, viewer]
    
  • How do I use map, filter and foreach with Kotlin collections?

    In Kotlin collections:

    • map transforms each element into a new value.
    • filter keeps only elements that match a condition.
    • forEach performs an action for each element.

    map: transform elements

    Use map when you want to create a new collection by changing each item.

    val numbers = listOf(1, 2, 3, 4)
    
    val doubled = numbers.map { number ->
        number * 2
    }
    
    println(doubled) // [2, 4, 6, 8]
    

    You can use it when the lambda has one parameter:

    val numbers = listOf(1, 2, 3, 4)
    
    val doubled = numbers.map { it * 2 }
    
    println(doubled) // [2, 4, 6, 8]
    

    filter: keep matching elements

    Use filter when you want only items that satisfy a condition.

    val numbers = listOf(1, 2, 3, 4, 5, 6)
    
    val evenNumbers = numbers.filter { it % 2 == 0 }
    
    println(evenNumbers) // [2, 4, 6]
    

    Another example with strings:

    val names = listOf("Alice", "Bob", "Charlie", "Anna")
    
    val namesStartingWithA = names.filter { it.startsWith("A") }
    
    println(namesStartingWithA) // [Alice, Anna]
    

    forEach: perform an action

    Use forEach when you want to do something with each element, such as printing.

    val names = listOf("Alice", "Bob", "Charlie")
    
    names.forEach { name ->
        println(name)
    }
    

    Using it:

    val names = listOf("Alice", "Bob", "Charlie")
    
    names.forEach {
        println(it)
    }
    

    Chaining them together

    You can combine filter, map, and forEach.

    val numbers = listOf(1, 2, 3, 4, 5, 6)
    
    numbers
        .filter { it % 2 == 0 }
        .map { it * 10 }
        .forEach { println(it) }
    

    Output:

    20
    40
    60
    

    This means:

    1. Keep only even numbers: [2, 4, 6]
    2. Multiply each by 10: [20, 40, 60]
    3. Print each result

    Example with objects

    data class User(
        val name: String,
        val age: Int
    )
    
    val users = listOf(
        User("Alice", 25),
        User("Bob", 17),
        User("Charlie", 30)
    )
    
    val adultNames = users
        .filter { it.age >= 18 }
        .map { it.name }
    
    println(adultNames) // [Alice, Charlie]
    

    Important difference

    map and filter return new collections:

    val numbers = listOf(1, 2, 3)
    
    val doubled = numbers.map { it * 2 }
    
    println(numbers) // [1, 2, 3]
    println(doubled) // [2, 4, 6]
    

    forEach is usually used for side effects and does not create a transformed list:

    val numbers = listOf(1, 2, 3)
    
    numbers.forEach { println(it) }
    

    Quick summary

    val numbers = listOf(1, 2, 3, 4, 5)
    
    val squared = numbers.map { it * it }
    // [1, 4, 9, 16, 25]
    
    val greaterThanTwo = numbers.filter { it > 2 }
    // [3, 4, 5]
    
    numbers.forEach { println(it) }
    // Prints each number
    

    Use:

    • map when you want to transform values
    • filter when you want to select values
    • forEach when you want to perform an action for each value
  • How do I convert arrays to lists and vice versa in Kotlin?

    In Kotlin, you can convert arrays to lists and lists to arrays using standard library functions.

    Array to List

    Use toList():

    val array = arrayOf("a", "b", "c")
    
    val list: List<String> = array.toList()
    
    println(list) // [a, b, c]
    

    If you want a mutable list, use toMutableList():

    val array = arrayOf("a", "b", "c")
    
    val mutableList: MutableList<String> = array.toMutableList()
    
    mutableList.add("d")
    
    println(mutableList) // [a, b, c, d]
    

    List to Array

    Use toTypedArray():

    val list = listOf("a", "b", "c")
    
    val array: Array<String> = list.toTypedArray()
    
    println(array.contentToString()) // [a, b, c]
    

    Primitive Arrays

    Kotlin has special primitive array types like IntArray, DoubleArray, and BooleanArray.

    IntArray to List

    val intArray = intArrayOf(1, 2, 3)
    
    val list: List<Int> = intArray.toList()
    
    println(list) // [1, 2, 3]
    

    List<Int> to IntArray

    Use toIntArray():

    val list = listOf(1, 2, 3)
    
    val intArray: IntArray = list.toIntArray()
    
    println(intArray.contentToString()) // [1, 2, 3]
    

    Other primitive conversions work similarly:

    val doubles: List<Double> = listOf(1.1, 2.2, 3.3)
    val doubleArray: DoubleArray = doubles.toDoubleArray()
    
    val booleans: List<Boolean> = listOf(true, false)
    val booleanArray: BooleanArray = booleans.toBooleanArray()
    

    Important Note: asList()

    For object arrays, you can also use asList():

    val array = arrayOf("a", "b", "c")
    
    val list = array.asList()
    

    The difference is:

    val array = arrayOf("a", "b", "c")
    
    val copiedList = array.toList()
    val backedList = array.asList()
    
    • toList() creates a new list copy.
    • asList() returns a list backed by the original array.

    Example:

    val array = arrayOf("a", "b", "c")
    val list = array.asList()
    
    array[0] = "z"
    
    println(list) // [z, b, c]
    

    So in most cases, use:

    array.toList()
    list.toTypedArray()
    

    And for primitive types:

    intArray.toList()
    list.toIntArray()
    
  • How do I sort a list of values in Kotlin?

    In Kotlin, you usually sort a list with sorted():

    val numbers = listOf(5, 2, 8, 1)
    
    val sortedNumbers = numbers.sorted()
    
    println(sortedNumbers) // [1, 2, 5, 8]
    

    sorted() returns a new sorted list and does not change the original list.

    For descending order, use sortedDescending():

    val numbers = listOf(5, 2, 8, 1)
    
    val sortedDescending = numbers.sortedDescending()
    
    println(sortedDescending) // [8, 5, 2, 1]
    

    For objects, sort by a property with sortedBy():

    data class Person(val name: String, val age: Int)
    
    val people = listOf(
        Person("Alice", 30),
        Person("Bob", 25),
        Person("Charlie", 35)
    )
    
    val sortedByAge = people.sortedBy { it.age }
    
    println(sortedByAge)
    // [Person(name=Bob, age=25), Person(name=Alice, age=30), Person(name=Charlie, age=35)]
    

    If you have a mutable list and want to sort it in place, use sort():

    val numbers = mutableListOf(5, 2, 8, 1)
    
    numbers.sort()
    
    println(numbers) // [1, 2, 5, 8]
    

    Quick summary:

    • sorted() — returns a new ascending list
    • sortedDescending() — returns a new descending list
    • sortedBy { ... } — sorts by a property
    • sort() — sorts a mutable list in place
  • How do I check if a collection is empty or contains an element in Kotlin?

    In Kotlin, use isEmpty() / isNotEmpty() to check whether a collection has elements, and use in, contains(), or map-specific methods to check contents.

    val names = listOf("Alice", "Bob")
    
    println(names.isEmpty())     // false
    println(names.isNotEmpty())  // true
    
    println("Alice" in names)    // true
    println("Charlie" !in names) // true
    

    For lists and sets:

    val numbers = setOf(1, 2, 3)
    
    if (numbers.isNotEmpty()) {
        println("The set has elements")
    }
    
    if (2 in numbers) {
        println("The set contains 2")
    }
    

    For maps, check keys or values explicitly:

    val ages = mapOf(
        "Alice" to 25,
        "Bob" to 30
    )
    
    println(ages.isEmpty())              // false
    println(ages.containsKey("Alice"))   // true
    println(ages.containsValue(30))      // true
    println("Bob" in ages)               // true, checks keys
    

    You can also use contains():

    val items = listOf("Book", "Pen")
    
    println(items.contains("Book")) // true
    

    In short:

    • collection.isEmpty() checks if it has no elements
    • collection.isNotEmpty() checks if it has at least one element
    • element in collection checks if an element exists
    • element !in collection checks if an element does not exist
    • map.containsKey(key) checks for a key
    • map.containsValue(value) checks for a value
  • How do I access elements safely in Kotlin collections?

    In Kotlin, you can access collection elements safely by using functions that return null instead of throwing exceptions when an index/key is missing.

    Lists / arrays: use getOrNull

    val items = listOf("A", "B", "C")
    
    val first = items.getOrNull(0)   // "A"
    val missing = items.getOrNull(10) // null
    

    This is safer than:

    val missing = items[10] // Throws IndexOutOfBoundsException
    

    You can combine it with the Elvis operator:

    val value = items.getOrNull(10) ?: "Default value"
    

    Lists / arrays: use getOrElse

    If you want a fallback value:

    val items = listOf("A", "B", "C")
    
    val value = items.getOrElse(10) { index ->
        "No item at index $index"
    }
    

    First / last elements safely

    Instead of first() or last(), which throw if the collection is empty, use:

    val items = emptyList<String>()
    
    val first = items.firstOrNull()
    val last = items.lastOrNull()
    

    With a predicate:

    val numbers = listOf(1, 2, 3, 4)
    
    val firstEven = numbers.firstOrNull { it % 2 == 0 } // 2
    val firstBig = numbers.firstOrNull { it > 10 }      // null
    

    Single element safely

    Use singleOrNull() when you expect exactly one matching element:

    val users = listOf("Alice", "Bob")
    
    val onlyAlice = users.singleOrNull { it == "Alice" } // "Alice"
    val onlyZoe = users.singleOrNull { it == "Zoe" }     // null
    

    Note: singleOrNull() also returns null if there is more than one match.

    Maps: safe access by key

    Map access already returns nullable values:

    val ages = mapOf("Alice" to 30)
    
    val aliceAge = ages["Alice"] // 30
    val bobAge = ages["Bob"]     // null
    

    Use a default if needed:

    val bobAge = ages["Bob"] ?: 0
    

    Or use getOrDefault:

    val bobAge = ages.getOrDefault("Bob", 0)
    

    Check bounds manually if needed

    val items = listOf("A", "B", "C")
    val index = 2
    
    if (index in items.indices) {
        println(items[index])
    }
    

    Summary

    Prefer these safe APIs:

    list.getOrNull(index)
    list.getOrElse(index) { default }
    list.firstOrNull()
    list.lastOrNull()
    list.singleOrNull()
    map[key] ?: default
    map.getOrDefault(key, default)
    

    Use direct indexing like list[index] only when you are certain the index is valid.

  • How do I loop through collections using for, foreach and indices in Kotlin?

    In Kotlin, you can loop through collections in several common ways depending on whether you need the element, the index, or both.

    1. Using for

    Use for when you want a simple, readable loop over elements.

    val names = listOf("Alice", "Bob", "Charlie")
    
    for (name in names) {
        println(name)
    }
    

    Output:

    Alice
    Bob
    Charlie
    

    This works with many Kotlin types, including:

    val numbers = arrayOf(1, 2, 3)
    
    for (number in numbers) {
        println(number)
    }
    

    2. Using forEach

    Use forEach when you prefer a functional style.

    val names = listOf("Alice", "Bob", "Charlie")
    
    names.forEach { name ->
        println(name)
    }
    

    If the lambda has only one parameter, you can use it:

    names.forEach {
        println(it)
    }
    

    forEach is useful for concise operations, but a regular for loop is often clearer if you need break, continue, or more complex control flow.


    3. Looping with indices

    Use indices when you need the index of each element.

    val names = listOf("Alice", "Bob", "Charlie")
    
    for (i in names.indices) {
        println("Index $i: ${names[i]}")
    }
    

    Output:

    Index 0: Alice
    Index 1: Bob
    Index 2: Charlie
    

    indices gives the valid index range for the collection, such as 0..lastIndex.


    4. Using withIndex

    If you need both the index and the value, withIndex() is often cleaner than indexing manually.

    val names = listOf("Alice", "Bob", "Charlie")
    
    for ((index, name) in names.withIndex()) {
        println("Index $index: $name")
    }
    

    5. Using forEachIndexed

    The forEach equivalent for index + value is forEachIndexed.

    val names = listOf("Alice", "Bob", "Charlie")
    
    names.forEachIndexed { index, name ->
        println("Index $index: $name")
    }
    

    Summary

    val items = listOf("A", "B", "C")
    
    // Element only
    for (item in items) {
        println(item)
    }
    
    // Element only, functional style
    items.forEach { item ->
        println(item)
    }
    
    // Index only / index-based access
    for (i in items.indices) {
        println("items[$i] = ${items[i]}")
    }
    
    // Index and value
    for ((index, item) in items.withIndex()) {
        println("$index -> $item")
    }
    
    // Index and value, functional style
    items.forEachIndexed { index, item ->
        println("$index -> $item")
    }
    

    Use:

    • for (item in items) for simple iteration
    • items.forEach { ... } for concise functional-style iteration
    • items.indices when you need index-based access
    • withIndex() or forEachIndexed when you need both index and value
  • How do I create and use lists, sets, and maps in Kotlin?

    In Kotlin, the main collection types are List, Set, and Map.

    Kotlin provides both read-only and mutable versions:

    Collection Read-only Mutable
    List List<T> MutableList<T>
    Set Set<T> MutableSet<T>
    Map Map<K, V> MutableMap<K, V>

    Lists

    A list is an ordered collection. It can contain duplicate elements.

    Read-only list

    val numbers = listOf(1, 2, 3, 3)
    
    println(numbers[0])        // 1
    println(numbers.size)      // 4
    println(numbers.contains(2)) // true
    

    You cannot add or remove items from a read-only List.

    val names = listOf("Alice", "Bob", "Charlie")
    
    for (name in names) {
        println(name)
    }
    

    Mutable list

    val names = mutableListOf("Alice", "Bob")
    
    names.add("Charlie")
    names.remove("Alice")
    names[0] = "Bobby"
    
    println(names) // [Bobby, Charlie]
    

    You can also create an empty mutable list:

    val items = mutableListOf<String>()
    
    items.add("Book")
    items.add("Pen")
    
    println(items) // [Book, Pen]
    

    Sets

    A set is a collection of unique elements. It does not allow duplicates.

    Read-only set

    val numbers = setOf(1, 2, 3, 3)
    
    println(numbers) // [1, 2, 3]
    println(2 in numbers) // true
    

    Mutable set

    val fruits = mutableSetOf("Apple", "Banana")
    
    fruits.add("Orange")
    fruits.add("Apple") // Duplicate, ignored
    fruits.remove("Banana")
    
    println(fruits) // [Apple, Orange]
    

    Empty mutable set:

    val ids = mutableSetOf<Int>()
    
    ids.add(101)
    ids.add(102)
    
    println(ids) // [101, 102]
    

    Maps

    A map stores key-value pairs. Each key is unique.

    Read-only map

    val ages = mapOf(
        "Alice" to 25,
        "Bob" to 30,
        "Charlie" to 35
    )
    
    println(ages["Alice"]) // 25
    println(ages["Unknown"]) // null
    println(ages.containsKey("Bob")) // true
    println(ages.containsValue(30)) // true
    

    Mutable map

    val scores = mutableMapOf(
        "Alice" to 90,
        "Bob" to 85
    )
    
    scores["Charlie"] = 95
    scores["Alice"] = 100
    scores.remove("Bob")
    
    println(scores) // {Alice=100, Charlie=95}
    

    Empty mutable map:

    val phoneBook = mutableMapOf<String, String>()
    
    phoneBook["Alice"] = "123-456"
    phoneBook["Bob"] = "987-654"
    
    println(phoneBook["Alice"]) // 123-456
    

    Common operations

    Iterating over a list or set

    val colors = listOf("Red", "Green", "Blue")
    
    for (color in colors) {
        println(color)
    }
    

    Iterating over a map

    val ages = mapOf(
        "Alice" to 25,
        "Bob" to 30
    )
    
    for ((name, age) in ages) {
        println("$name is $age years old")
    }
    

    Filtering

    val numbers = listOf(1, 2, 3, 4, 5, 6)
    
    val evenNumbers = numbers.filter { it % 2 == 0 }
    
    println(evenNumbers) // [2, 4, 6]
    

    Mapping values

    val names = listOf("alice", "bob", "charlie")
    
    val uppercaseNames = names.map { it.uppercase() }
    
    println(uppercaseNames) // [ALICE, BOB, CHARLIE]
    

    Sorting

    val numbers = listOf(5, 2, 8, 1)
    
    val sorted = numbers.sorted()
    
    println(sorted) // [1, 2, 5, 8]
    

    Checking contents

    val names = listOf("Alice", "Bob")
    
    println("Alice" in names) // true
    println("Charlie" !in names) // true
    

    Choosing between them

    Use a List when:

    • Order matters
    • Duplicates are allowed
    • You access elements by index
    val tasks = listOf("Write", "Test", "Deploy")
    

    Use a Set when:

    • Values must be unique
    • You mainly check whether something exists
    val uniqueTags = setOf("kotlin", "backend", "api")
    

    Use a Map when:

    • You need key-value lookup
    • Each key maps to one value
    val userRoles = mapOf(
        1 to "Admin",
        2 to "Editor",
        3 to "Viewer"
    )
    

    Quick summary

    val readOnlyList = listOf("A", "B", "C")
    val mutableList = mutableListOf("A", "B")
    mutableList.add("C")
    
    val readOnlySet = setOf("A", "B", "A") // [A, B]
    val mutableSet = mutableSetOf("A", "B")
    mutableSet.add("C")
    
    val readOnlyMap = mapOf("Alice" to 25, "Bob" to 30)
    val mutableMap = mutableMapOf("Alice" to 25)
    mutableMap["Bob"] = 30
    

    In short:

    • listOf() creates a read-only list
    • mutableListOf() creates a mutable list
    • setOf() creates a read-only set
    • mutableSetOf() creates a mutable set
    • mapOf() creates a read-only map
    • mutableMapOf() creates a mutable map
  • How do I implement the singleton pattern idiomatically using Kotlin’s object declaration?

    In Kotlin, the idiomatic way to implement the Singleton pattern is to use an object declaration.

    object AppConfig {
        val appName = "MyApp"
    
        fun printConfig() {
            println("App name: $appName")
        }
    }
    

    You use it directly by its name:

    fun main() {
        AppConfig.printConfig()
        println(AppConfig.appName)
    }
    

    Kotlin guarantees that an object declaration:

    • has exactly one instance
    • is initialized lazily, when first accessed
    • is thread-safe
    • can contain properties, functions, initialization blocks, and implement interfaces

    Example with initialization:

    object DatabaseManager {
        init {
            println("DatabaseManager initialized")
        }
    
        fun connect() {
            println("Connecting to database...")
        }
    }
    

    Usage:

    fun main() {
        DatabaseManager.connect()
    }
    

    If you need a singleton that implements an interface:

    interface Logger {
        fun log(message: String)
    }
    
    object ConsoleLogger : Logger {
        override fun log(message: String) {
            println("[LOG] $message")
        }
    }
    

    Usage:

    fun main() {
        ConsoleLogger.log("Application started")
    }
    

    So instead of writing a private constructor and static getInstance() method like in Java, Kotlin’s idiomatic singleton is simply:

    object MySingleton {
        fun doSomething() {
            println("Doing something")
        }
    }
    
  • How do I mix OOP with functional programming using Kotlin’s design patterns?

    You can mix OOP and functional programming in Kotlin by using objects/classes to model state, identity, boundaries, and domain concepts, while using functions/lambdas to model behavior, transformation, policies, and workflows.

    Kotlin is especially good at this because it supports:

    • Classes, interfaces, inheritance, encapsulation
    • Data classes and sealed classes
    • Lambdas and higher-order functions
    • Immutability with val
    • Extension functions
    • Scope functions like let, run, also, apply
    • Collection pipelines like map, filter, fold

    1. Use OOP for domain models, FP for transformations

    A common Kotlin style is to model entities with classes, then transform them using pure functions.

    data class User(
        val id: Long,
        val name: String,
        val age: Int,
        val isActive: Boolean
    )
    
    fun User.canReceivePromotion(): Boolean =
        isActive && age >= 18
    
    fun promoteEligibleUsers(users: List<User>): List<User> =
        users.filter { it.canReceivePromotion() }
    

    Here:

    • User is an OOP-style domain object.
    • canReceivePromotion is behavior attached through an extension function.
    • filter expresses functional transformation over a collection.

    This avoids writing procedural loops like:

    val result = mutableListOf<User>()
    for (user in users) {
        if (user.isActive && user.age >= 18) {
            result.add(user)
        }
    }
    

    Prefer:

    val result = users.filter { it.isActive && it.age >= 18 }
    

    2. Prefer immutable objects

    Functional programming favors immutability. In Kotlin, use val and data class.copy().

    data class Order(
        val id: Long,
        val status: OrderStatus,
        val total: Double
    )
    
    enum class OrderStatus {
        Draft,
        Paid,
        Shipped,
        Cancelled
    }
    
    fun markAsPaid(order: Order): Order =
        order.copy(status = OrderStatus.Paid)
    

    Instead of mutating the existing object:

    class MutableOrder {
        var status: OrderStatus = OrderStatus.Draft
    }
    

    Prefer creating a new value:

    val paidOrder = order.copy(status = OrderStatus.Paid)
    

    This makes code easier to test, reason about, and safely use in concurrent contexts.


    3. Use interfaces plus lambdas for Strategy pattern

    The classic OOP Strategy pattern uses an interface:

    interface DiscountStrategy {
        fun apply(total: Double): Double
    }
    
    class NoDiscount : DiscountStrategy {
        override fun apply(total: Double): Double = total
    }
    
    class PercentageDiscount(
        private val percent: Double
    ) : DiscountStrategy {
        override fun apply(total: Double): Double =
            total * (1 - percent)
    }
    

    In Kotlin, if the behavior is simple, you can replace the interface with a function type:

    typealias DiscountStrategy = (Double) -> Double
    
    val noDiscount: DiscountStrategy = { total -> total }
    
    fun percentageDiscount(percent: Double): DiscountStrategy =
        { total -> total * (1 - percent) }
    
    class CheckoutService(
        private val discountStrategy: DiscountStrategy
    ) {
        fun checkout(total: Double): Double =
            discountStrategy(total)
    }
    

    Usage:

    val service = CheckoutService(
        discountStrategy = percentageDiscount(0.15)
    )
    
    val finalTotal = service.checkout(100.0)
    println(finalTotal) // 85.0
    

    This is a good example of mixing both styles:

    • CheckoutService is object-oriented.
    • DiscountStrategy is functional.
    • The behavior is injected as a function.

    Use an interface when the strategy has multiple methods or meaningful identity. Use a function type when it is just one operation.


    4. Use sealed classes for type-safe state and results

    Instead of nullable values, error codes, or large inheritance hierarchies, Kotlin often uses sealed classes.

    sealed class PaymentResult {
        data class Success(val transactionId: String) : PaymentResult()
        data class Failure(val reason: String) : PaymentResult()
        data object Pending : PaymentResult()
    }
    

    Then handle all possible cases with when:

    fun describe(result: PaymentResult): String =
        when (result) {
            is PaymentResult.Success ->
                "Payment completed: ${result.transactionId}"
    
            is PaymentResult.Failure ->
                "Payment failed: ${result.reason}"
    
            PaymentResult.Pending ->
                "Payment is pending"
        }
    

    This combines:

    • OOP-style polymorphic modeling
    • FP-style expression-based branching
    • Compile-time exhaustiveness checking

    This is often cleaner than:

    class PaymentResult(
        val success: Boolean,
        val errorMessage: String?,
        val transactionId: String?
    )
    

    because that structure can represent invalid states.


    5. Replace some Template Method patterns with higher-order functions

    Classic OOP Template Method might look like this:

    abstract class ReportGenerator {
        fun generate(): String {
            val data = loadData()
            val formatted = format(data)
            return export(formatted)
        }
    
        protected abstract fun loadData(): List<String>
        protected abstract fun format(data: List<String>): String
        protected abstract fun export(content: String): String
    }
    

    In Kotlin, you can often use higher-order functions:

    class ReportGenerator(
        private val loadData: () -> List<String>,
        private val format: (List<String>) -> String,
        private val export: (String) -> String
    ) {
        fun generate(): String {
            val data = loadData()
            val formatted = format(data)
            return export(formatted)
        }
    }
    

    Usage:

    val generator = ReportGenerator(
        loadData = { listOf("A", "B", "C") },
        format = { data -> data.joinToString(separator = "\n") },
        export = { content -> "Report:\n$content" }
    )
    
    println(generator.generate())
    

    This avoids subclassing when all you need is customizable behavior.


    6. Use composition over inheritance

    Kotlin works well when you compose small behaviors instead of building deep inheritance trees.

    interface Logger {
        fun log(message: String)
    }
    
    class ConsoleLogger : Logger {
        override fun log(message: String) {
            println(message)
        }
    }
    
    class UserService(
        private val logger: Logger,
        private val validateUser: (User) -> Boolean
    ) {
        fun register(user: User) {
            if (!validateUser(user)) {
                logger.log("Invalid user: ${user.name}")
                return
            }
    
            logger.log("Registered user: ${user.name}")
        }
    }
    

    Here:

    • Logger is an OOP abstraction.
    • validateUser is a functional dependency.
    • UserService composes both.

    Usage:

    val service = UserService(
        logger = ConsoleLogger(),
        validateUser = { user -> user.age >= 18 && user.isActive }
    )
    

    This is flexible without excessive inheritance.


    7. Use extension functions to add behavior without modifying classes

    Extension functions are useful when you want functional-style transformations around OOP models.

    data class Product(
        val name: String,
        val price: Double,
        val category: String
    )
    
    fun Product.withTax(rate: Double): Product =
        copy(price = price * (1 + rate))
    
    fun Product.isExpensive(): Boolean =
        price > 100.0
    

    Usage:

    val products = listOf(
        Product("Keyboard", 80.0, "Electronics"),
        Product("Monitor", 250.0, "Electronics")
    )
    
    val taxedExpensiveProducts =
        products
            .map { it.withTax(0.2) }
            .filter { it.isExpensive() }
    

    This keeps your domain model clean while allowing expressive pipelines.


    8. Use functional pipelines inside object-oriented services

    You do not need to choose between “service classes” and functional pipelines. They combine naturally.

    class InvoiceService {
        fun calculateTotal(items: List<InvoiceItem>): Double =
            items
                .filter { it.quantity > 0 }
                .map { it.price * it.quantity }
                .sum()
    }
    
    data class InvoiceItem(
        val name: String,
        val price: Double,
        val quantity: Int
    )
    

    The class defines the business capability. The method implementation uses functional collection operations.


    9. Use command objects or lambdas for Command pattern

    Classic OOP Command pattern:

    interface Command {
        fun execute()
    }
    
    class PrintCommand(
        private val message: String
    ) : Command {
        override fun execute() {
            println(message)
        }
    }
    

    Kotlin functional version:

    typealias Command = () -> Unit
    
    val printCommand: Command = {
        println("Hello from command")
    }
    
    fun runCommand(command: Command) {
        command()
    }
    

    Usage:

    runCommand {
        println("Executing inline command")
    }
    

    Use a class-based command when the command has state, metadata, undo behavior, or lifecycle. Use a lambda when it is just executable behavior.


    10. Use Repository as OOP boundary, FP for business rules

    A practical architecture pattern is:

    • Repositories/gateways: OOP interfaces
    • Use cases/services: classes
    • Business rules: pure functions
    • Data transformations: functional pipelines
    interface UserRepository {
        fun findAll(): List<User>
        fun save(user: User)
    }
    
    class ActivateUsersUseCase(
        private val repository: UserRepository
    ) {
        fun execute() {
            repository
                .findAll()
                .filter { shouldActivate(it) }
                .map { it.copy(isActive = true) }
                .forEach { repository.save(it) }
        }
    
        private fun shouldActivate(user: User): Boolean =
            !user.isActive && user.age >= 18
    }
    

    This gives you:

    • Testable boundaries
    • Clear dependency injection
    • Pure business logic where possible
    • OOP structure where useful

    11. Map design patterns to Kotlin idioms

    Many GoF-style design patterns become simpler in Kotlin.

    Classic Pattern Kotlin-Friendly Approach
    Strategy Function type, lambda, or interface
    Command () -> Unit or command class
    Factory Top-level function, companion object, or lambda
    Template Method Higher-order function composition
    Decorator Composition, extension functions, wrapper classes
    Observer Function callbacks, Flow, listener interfaces
    State Sealed classes plus when
    Visitor Sealed classes plus exhaustive when
    Builder Named/default arguments, DSL builders
    Adapter Extension functions or wrapper classes

    Example factory:

    sealed class Notification {
        data class Email(val address: String) : Notification()
        data class Sms(val phoneNumber: String) : Notification()
    }
    
    fun createNotification(type: String, target: String): Notification =
        when (type) {
            "email" -> Notification.Email(target)
            "sms" -> Notification.Sms(target)
            else -> error("Unsupported notification type: $type")
        }
    

    12. A practical mixed-style example

    data class CartItem(
        val name: String,
        val price: Double,
        val quantity: Int
    )
    
    data class Cart(
        val items: List<CartItem>
    )
    
    typealias PricingRule = (CartItem) -> Double
    
    class CartCalculator(
        private val pricingRule: PricingRule
    ) {
        fun total(cart: Cart): Double =
            cart.items.sumOf { item ->
                pricingRule(item) * item.quantity
            }
    }
    
    fun standardPricing(item: CartItem): Double =
        item.price
    
    fun discountedPricing(discount: Double): PricingRule =
        { item -> item.price * (1 - discount) }
    

    Usage:

    val cart = Cart(
        listOf(
            CartItem("Book", 20.0, 2),
            CartItem("Pen", 2.0, 5)
        )
    )
    
    val calculator = CartCalculator(
        pricingRule = discountedPricing(0.10)
    )
    
    println(calculator.total(cart)) // 45.0
    

    What this demonstrates:

    • Cart and CartItem are OOP/value models.
    • PricingRule is a functional strategy.
    • CartCalculator is an object-oriented service.
    • The total calculation uses functional collection operations.

    General guidelines

    Use OOP when you need:

    • Domain concepts with identity
    • Encapsulation
    • Long-lived services
    • Polymorphic boundaries
    • Dependency injection
    • External system boundaries like repositories, APIs, databases

    Use functional programming when you need:

    • Data transformations
    • Stateless business rules
    • Reusable policies
    • Collection processing
    • Validation
    • Pipelines
    • Behavior injection

    A good Kotlin rule of thumb:

    Model your domain with objects, model your behavior with functions, and keep state immutable unless mutation is clearly necessary.

    That gives you Kotlin code that is expressive, testable, and maintainable.