The classic bug is a stale capture: an effect keyed on Unit keeps its first lambda. Both timers log label, and Change promo switches it to "Clearance" before they fire:
@Composable
fun StaleTimer(onTimeout: () -> Unit) {
LaunchedEffect(Unit) { delay(3000); onTimeout() } // keeps the first lambda forever
}
@Composable
fun FreshTimer(onTimeout: () -> Unit) {
val latest by rememberUpdatedState(onTimeout) // updated on every recomposition
LaunchedEffect(Unit) { delay(3000); latest() }
}Output
D/BookNestFx: stale timer sees 'Autumn sale' D/BookNestFx: fresh timer sees 'Clearance'
rememberUpdatedState reads the latest value without restarting, like React 19.2 7,897 's useEffectEvent. Other bugs that compile: scope.launch in a composable's body repeats on every recomposition (use LaunchedEffect), a value read but not keyed leaves old data on screen, and catch (e: Exception) swallows cancellation. Rotate each screen and navigate away and back to flush out missing cleanup.