Ban GlobalScope.launch in Code Reviews
Your AI assistant just leaked a coroutine, and you will never see the error. GlobalScope.launch compiles, runs, and looks harmless in a diff — which is exactly why AI-generated Kotlin reaches for it so often. Make it an automatic review reject.
Why GlobalScope is a leak by design
GlobalScope is a CoroutineScope with no Job behind it. Anything you launch there lives for the whole application lifetime and is tied to nothing that can cancel it. The official Kotlin docs flag three concrete failure modes:
- Uncontrolled work — the coroutine keeps running after the thing that started it (a screen, a request, a job) is gone.
- Resource leaks — a coroutine that never resumes or gets cancelled holds its memory, sockets, and file handles forever.
- Silent crashes — there is no parent to receive failures, so without an explicit
CoroutineExceptionHandlerthe exception surfaces platform-specifically or not at all. That is the "you will never see the error" part.
The API is so easy to misuse that Kotlin marks it @DelicateCoroutinesApi: you must explicitly opt in with @OptIn(DelicateCoroutinesApi::class) to use it without a warning. Treat that annotation in a diff as a red flag, not a formality.
What structured concurrency buys you
Structured concurrency means coroutines form a tree: every coroutine has a parent, a parent waits for its children, and cancelling the parent recursively cancels every child. Two guarantees fall out of that:
- Work stops when its owner stops. Cancel the scope, and everything launched in it is cancelled — no orphans.
- Errors have somewhere to go. A child's failure propagates to its parent instead of vanishing.
GlobalScope.launch opts out of the entire tree. That is the whole bug.
The review checklist
| You see in the diff | Ask for instead |
|---|---|
GlobalScope.launch { ... } | A scope owned by the caller (viewModelScope, an injected CoroutineScope, coroutineScope { }) |
Sequential work wrapped in launch | A plain suspend function — no new coroutine needed |
Concurrent work inside a suspend function | coroutineScope { launch { } ; launch { } } |
catch (e: CancellationException) without rethrow | Rethrow it — swallowing it breaks cancellation for the whole branch |
GlobalScope hardcoded in a class | An injected CoroutineScope constructor parameter (Android's official best practice — it also makes the class testable) |
That last row matters for AI-generated code specifically: hardcoding GlobalScope usually means hardcoded Dispatchers too, and code that no test can control.
The cancellation trap
Cancellation in coroutines is cooperative and travels as a CancellationException. Two ways generated code silently breaks it:
- A broad
catch (e: Exception)around adelayor network call catches theCancellationExceptionand keeps going. Always rethrow it. - CPU-bound loops with no suspension point never observe cancellation at all. Add
ensureActive()oryield()inside long loops.
Either bug plus GlobalScope compounds: unowned work that also refuses to die.
Power-user: enforce the ban with a linter
Don't rely on human reviewers to spot it. detekt ships a GlobalCoroutineUsage rule (coroutines rule set) that flags GlobalScope.launch and GlobalScope.async — it is off by default, so turn it on:
# detekt.yml
coroutines:
GlobalCoroutineUsage:
active: true
Then put the same rule in your AI assistant's project instructions ("never use GlobalScope; launch in a scope owned by the caller"), so the code arrives correct instead of getting bounced in review. The one legitimate exception the Kotlin docs allow: a true application-lifetime background job, launched once, with an explicit CoroutineExceptionHandler and the @OptIn annotation making the choice visible.
Resources
- GlobalScope — kotlinx.coroutines API docs — the delicate-API warning, the three pitfalls, and the replacement patterns
- Coroutine basics: structured concurrency — why parent/child lifecycles prevent leaks
- Cancellation and timeouts — cooperative cancellation,
CancellationException, and why you must rethrow it - Android coroutines best practices — inject scopes, avoid
GlobalScope,viewModelScopeguidance - detekt coroutines rule set — GlobalCoroutineUsage — the lint rule that automates the ban
Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.