LaunchedEffect and its keys

Jetpack Compose Mid-level Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Predict the output

userId changes from 1 to 2 while the first load is in progress. What happens?

@Composable
fun Profile(userId: String, onLoaded: (User) -> Unit) {
    LaunchedEffect(userId) {
        val user = fetchUser(userId)
        onLoaded(user)
    }
}
Options
Hint 1 of 1

What does a changed key do to the running coroutine?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A The load for 1 is cancelled and a load for 2 starts

Correct. A key change cancels the running effect and launches a new one.

Why not the others?
  • B Both loads run and both call onLoaded. The first coroutine is cancelled, so it never reaches onLoaded.
  • C Nothing happens until the first load finishes. Key changes restart the effect immediately.
  • D The effect doesn't restart because keys only matter on first composition. Keys are compared on every recomposition.

30-second answer

LaunchedEffect(key) { } starts a coroutine when it enters composition and cancels it when it leaves. If any key changes, the running coroutine is cancelled and a new one starts. Use it to run suspend work tied to the composable, such as showing a snackbar or reacting to an ID. It runs on the main thread, in the composition's coroutine context.

Practise saying it · 35 s. No recording, just a timer

Explain LaunchedEffect and keys.

Say your answer out loud, then check which of these points you covered:

  • Starts on enter, cancels on leave
  • Key change cancels and restarts
  • Runs on the main thread
  • Screen data belongs in the ViewModel

Deep dive

LaunchedEffect(Unit) runs once per entry into composition, not once per app run; rotation starts it again.

Keys should be the values the effect uses. Missing keys mean the effect keeps using stale values.

Heavy work inside should move off main with withContext, as in any coroutine.

Loading screen data usually belongs in the ViewModel, not in a LaunchedEffect, so it survives rotation.

Common mistakes

  • Loading data in LaunchedEffect(Unit) and reloading on every rotation.
  • Using a changing key by accident, like a lambda, and restarting the effect constantly.
  • Thinking the block runs on a background thread.

Senior take & follow-ups

Say 'cancel and restart on key change' and that it runs on main. Then point to the ViewModel for data loading.

Follow-up questions

Think of your answer first, then open the hint.

  1. How would you show a snackbar from state?

    Show hint

    LaunchedEffect(message) { snackbarHostState.showSnackbar(it); onShown() }.

  2. What if the effect needs the latest callback without restarting?

    Show hint

    rememberUpdatedState for that callback.

Compare on iOS: .task(id:) and built-in debouncing SwiftUI's .task(id:) also cancels and restarts when the id changes.

Suggest a fix if something here is wrong or out of date.