Kotlin Academy · Lesson

combine and zip: Merging Multiple Flows

Combine multiple Flows and zip their emissions together.

Lesson 3 of 413 steps

combine and zip: Merging Multiple Flows is a free Kotlin Academy lesson on CoddyKit. This is lesson 3 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, and your progress syncs across the web and the CoddyKit app. The Kotlin Academy course includes 4 lessons in total.

Why Merge Flows?

Real apps often need to combine multiple data streams: user preferences + network data, form fields + validation state. Kotlin Flow provides zip, combine, and merge for this.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val names = flowOf("Alice", "Bob", "Carol")
    val ages  = flowOf(30, 25, 35)
    names.zip(ages) { name, age -> "$name is $age" }
         .collect { println(it) }
}

zip: Paired Emissions

zip pairs emissions one-to-one in order. It stops when the shorter flow ends.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val a = flowOf(1, 2, 3, 4)   // 4 elements
    val b = flowOf("a", "b", "c")  // 3 elements — zip stops here
    a.zip(b) { n, s -> "$n-$s" }
     .collect { println(it) } // 1-a, 2-b, 3-c
}

combine: Latest Values

combine emits a new value whenever any of the flows emits, using the latest value from each. Both flows must have emitted at least once.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val flow1 = MutableStateFlow(1)
    val flow2 = MutableStateFlow("a")
    combine(flow1, flow2) { n, s -> "$n-$s" }
        .take(1)
        .collect { println(it) } // 1-a
}

combine vs zip Difference

zip: waits for matching pair. combine: fires on every update using latest values. Use combine for reactive UI (e.g., two form fields), zip for sequential pairing.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    // zip: 1-a, 2-b
    val nums = flowOf(1, 2).zip(flowOf("a", "b")) { n, s -> "$n-$s" }
    nums.collect { println("zip: $it") }

    // combine fires more:
    combine(flowOf(1, 2), flowOf("a")) { n, s -> "$n-$s" }
        .collect { println("combine: $it") }
}

merge: Interleaving Flows

merge emits from multiple flows as they arrive, without pairing — output order depends on emission timing.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val a = flow { emit("A1"); delay(50); emit("A2") }
    val b = flow { delay(25); emit("B1") }
    merge(a, b).collect { println(it) } // A1, B1, A2
}

combine with 3+ Flows

combine supports multiple flows. Pass them as vararg and the lambda receives all latest values.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val a = MutableStateFlow("hello")
    val b = MutableStateFlow(42)
    val c = MutableStateFlow(true)
    combine(a, b, c) { s, n, flag -> "$s-$n-$flag" }
        .take(1)
        .collect { println(it) } // hello-42-true
}

flatMapMerge: Concurrent Inner Flows

flatMapMerge maps each element to a flow and collects all inner flows concurrently, merging their outputs.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    (1..3).asFlow()
        .flatMapMerge { n ->
            flow { delay(100L - n * 10); emit("result-$n") }
        }
        .collect { println(it) }
}

flatMapLatest: Cancel on New Emission

flatMapLatest cancels the current inner flow when a new element arrives upstream — perfect for search-as-you-type.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    flowOf("k", "ko", "kot", "kotl", "kotli", "kotlin")
        .flatMapLatest { query ->
            flow {
                delay(50)  // simulate debounced search
                emit("Result for: $query")
            }
        }
        .collect { println(it) } // only "Result for: kotlin"
}

zip for Transaction Pairing

Use zip to pair events from two streams: e.g., user actions with server confirmations, ensuring 1-to-1 correspondence.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val orders = flowOf("Order1", "Order2", "Order3")
    val confirmations = flowOf("Confirmed", "Confirmed", "Rejected")
    orders.zip(confirmations) { order, status -> "$order: $status" }
          .collect { println(it) }
}

combine for Form Validation

Combine username and password flows to reactively compute form validity — updates whenever either field changes.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val username = MutableStateFlow("")
    val password = MutableStateFlow("")
    val isValid = combine(username, password) { u, p ->
        u.length >= 3 && p.length >= 8
    }
    username.value = "alice"
    password.value = "secure123"
    println(isValid.first()) // true
}

onEach + combine for Multi-Source Loading

Combine a loading flag with a data flow to produce a single UI state object from multiple independent sources.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
data class UI(val loading: Boolean, val data: String)
fun main() = runBlocking {
    val loading = MutableStateFlow(true)
    val data    = MutableStateFlow("")
    combine(loading, data) { l, d -> UI(l, d) }
        .take(2)
        .collect { println(it) }
    data.value = "result"
    loading.value = false
}

Quick Check

What is the key difference between zip and combine?

Recap

zip pairs emissions sequentially; combine fires on any update using latest values; merge interleaves independent flows; flatMapLatest cancels stale inner flows. Choose based on pairing vs. reactivity needs.

Free to start

Learn Kotlin with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
51
Lessons
203

Frequently Asked Questions

Is the “combine and zip: Merging Multiple Flows” lesson free?

Yes — any 3 lessons of the Kotlin Academy learning path, including “combine and zip: Merging Multiple Flows”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Kotlin Academy course includes 4 lessons in total.

What will I learn in “combine and zip: Merging Multiple Flows”?

Combine multiple Flows and zip their emissions together. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Kotlin Academy?

No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 3 of 4.

How long does the “combine and zip: Merging Multiple Flows” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Kotlin Academy lesson?

Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Flow Operators: map, filter, transform, and take
  2. catch and onCompletion: Error Handling in Flow
  3. combine and zip: Merging Multiple Flows
  4. flowOn and buffer: Context and Backpressure
← Back to Kotlin Academy