
Jetpack Compose LaunchedEffect는 Compose를 배우는 순간부터 자주 마주치지만, 실행 시점을 잘못 이해하면 같은 API 호출이 반복되거나 이벤트가 두 번 처리될 수 있습니다.
핵심은 화면을 그리는 recomposition과 외부 작업을 일으키는 side effect를 분리해서 보는 것입니다. 이 글은 Android 공식 Compose side effect 문서를 기준으로 LaunchedEffect의 key, coroutine, 중복 실행 실수를 정리합니다.

Jetpack Compose LaunchedEffect를 먼저 한 줄로 정리하면
LaunchedEffect는 composable이 composition에 들어왔을 때 coroutine을 시작하고, 지정한 key가 바뀌면 이전 coroutine을 취소한 뒤 새로 시작하는 effect API입니다.
@Composable
fun UserScreen(userId: String, viewModel: UserViewModel) {
LaunchedEffect(userId) {
viewModel.loadUser(userId)
}
}위 코드에서 recomposition이 일어난다고 매번 `loadUser`가 실행되지는 않습니다. `userId` key가 같으면 effect는 유지되고, key가 바뀌면 다시 실행됩니다.
recomposition과 effect 실행은 같은 말이 아니다
Compose의 recomposition은 UI를 다시 계산하는 과정입니다. 하지만 네트워크 요청, snackbar 표시, navigation 같은 일은 UI 계산 그 자체가 아니라 외부 세계에 영향을 주는 작업입니다.
그래서 composable 본문에 suspend 함수 호출이나 로그성 이벤트 처리를 바로 넣으면 위험합니다. UI가 다시 계산될 때마다 같은 작업이 반복될 수 있기 때문입니다.
@Composable
fun BadExample(viewModel: UserViewModel) {
// recomposition 때마다 호출될 수 있어 위험한 형태
viewModel.loadUser("42")
}key는 effect의 생명주기 기준이다
LaunchedEffect의 key는 ‘무엇이 바뀌면 이 작업을 다시 해야 하는가’를 표현합니다. key를 너무 넓게 잡으면 필요 없는 재실행이 늘고, 너무 좁게 잡으면 새 데이터가 와도 작업이 갱신되지 않습니다.
- `LaunchedEffect(Unit)`은 composition에 처음 들어올 때 한 번 실행되는 의도에 가깝다
- `LaunchedEffect(userId)`는 userId가 바뀔 때 다시 실행된다
- `LaunchedEffect(uiState)`처럼 큰 객체를 key로 두면 변경이 잦아질 수 있다
- 현재 값은 필요하지만 재시작 기준이 아니면 `rememberUpdatedState`를 검토한다
언제 LaunchedEffect를 쓰고 언제 쓰지 않을까
LaunchedEffect는 composition의 생명주기와 묶인 coroutine 작업에 잘 맞습니다. 화면 진입 시 데이터 로딩, 특정 상태 도달 시 snackbar 표시, navigation 이벤트 처리처럼 UI와 외부 작업 사이에 경계가 있을 때 유용합니다.
반대로 단순히 값을 계산하거나 화면에 보여줄 값을 파생하는 용도라면 LaunchedEffect보다 일반 Kotlin 코드, `remember`, `derivedStateOf` 같은 선택지가 더 자연스러울 수 있습니다.
중복 실행을 줄이는 실무 기준
- effect 안에 넣는 작업이 실제 side effect인지 먼저 확인한다
- key를 ‘다시 시작해야 하는 최소 기준’으로 좁힌다
- 이벤트는 ViewModel 상태와 소비 여부를 함께 설계한다
- API 호출은 화면 재진입, configuration change, navigation back stack을 함께 고려한다
- recomposition 로그와 effect 로그를 분리해서 디버깅한다
정리
Jetpack Compose LaunchedEffect를 이해할 때는 문법이나 기능 이름보다 문제가 생기는 경계를 먼저 보는 편이 좋습니다. 오늘 글의 기준은 ‘언제 다시 실행되고, 어디까지 공개되고, 어떤 권한이 남는가’처럼 실수 지점을 분리하는 것입니다.
함께 보면 좋은 내부 글은 Jetpack Compose remember는 무엇을 기억할까, collectAsStateWithLifecycle은 왜 필요할까, Compose 상태 호이스팅은 언제 해야 할까입니다. 외부 기준은 Android Developers – Side-effects in Compose, Android Developers – Thinking in Compose, Kotlin Docs – Coroutines basics를 확인했습니다.