Kotlin CoroutineScope Leak is a common issue faced by Android developers when coroutines are launched but not properly canceled, leading to memory leaks and unexpected app behavior. Understanding how to prevent leaks in CoroutineScope is essential for building stable and scalable Android applications. In this article, we will explore what causes CoroutineScope leaks, provide real-world examples, and share the best practices to avoid them.

What is CoroutineScope in Kotlin?
CoroutineScope in Kotlin defines the lifecycle of a coroutine. When you launch a coroutine within a scope, the coroutine will run until the scope itself is canceled. This is extremely useful in Android development because it allows developers to control when coroutines start and stop, ensuring efficient resource usage.
What is a CoroutineScope Leak?
A CoroutineScope leak happens when a coroutine continues running even after the component (such as an Activity or Fragment) that launched it has been destroyed. This results in memory leaks, wasted resources, and even crashes in some cases.
Example of a CoroutineScope Leak
Consider the following code inside an Android Activity:
class MainActivity : AppCompatActivity() { private val job = Job() private val scope = CoroutineScope(Dispatchers.IO + job) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) scope.launch { // Long-running network operation delay(10000) Log.d("CoroutineLeak", "Coroutine finished after 10 seconds") } } }
In this case, if the user closes the Activity before 10 seconds, the coroutine will still continue running in the background. Since the Activity is destroyed, this leads to a Kotlin CoroutineScope leak.
How to Prevent CoroutineScope Leaks
There are several ways to avoid leaks when using Kotlin coroutines. Let’s go through the most effective strategies.
1. Cancel CoroutineScope in onDestroy()
The simplest method is to cancel the coroutine when the lifecycle component (Activity/Fragment) is destroyed.
override fun onDestroy() { super.onDestroy() job.cancel() // Cancel the scope }
By canceling the job, all coroutines launched within that scope will also be canceled, preventing memory leaks.
2. Use lifecycleScope in Android
Android provides a built-in lifecycleScope
that automatically cancels coroutines when the LifecycleOwner (Activity or Fragment) is destroyed. This is the recommended approach.
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) lifecycleScope.launch { delay(10000) Log.d("SafeCoroutine", "This coroutine is automatically canceled") } } }
With lifecycleScope
, you don’t need to manually cancel coroutines, reducing the risk of leaks.
3. Use viewModelScope in ViewModel
If you are working with the MVVM architecture, launching coroutines inside the ViewModel using viewModelScope
is the safest choice.
class MainViewModel : ViewModel() { fun fetchData() { viewModelScope.launch { val result = repository.getData() Log.d("ViewModelScope", "Data fetched: $result") } } }
The viewModelScope
automatically cancels coroutines when the ViewModel is cleared, ensuring there are no leaks tied to UI components.
4. Avoid GlobalScope
Using GlobalScope.launch
is a bad practice in Android because the coroutine will outlive the lifecycle of the Activity or Fragment. Always prefer lifecycleScope
or viewModelScope
to tie coroutines to lifecycle events.
5. Use Structured Concurrency
Structured concurrency ensures that child coroutines are properly canceled when their parent scope is canceled. This approach makes your code cleaner and less prone to leaks.
suspend fun fetchData() = coroutineScope { val data1 = async { getDataFromApi1() } val data2 = async { getDataFromApi2() } data1.await() + data2.await() }
Here, if coroutineScope
is canceled, both data1
and data2
coroutines are also canceled automatically.
Comparison: With and Without lifecycleScope
Without lifecycleScope | With lifecycleScope |
---|---|
Requires manual cancellation in onDestroy() | Automatic cancellation when lifecycle is destroyed |
Higher chance of memory leaks | Safe and lifecycle-aware |
More boilerplate code | Cleaner and concise |
Best Practices to Prevent CoroutineScope Leaks
- Always tie your coroutines to lifecycle-aware scopes (
lifecycleScope
orviewModelScope
). - Cancel coroutines manually in
onDestroy()
if using custom scopes. - Avoid
GlobalScope
for Android apps. - Use structured concurrency whenever possible.
Conclusion
Preventing Kotlin CoroutineScope leaks is crucial for building stable Android applications. By leveraging lifecycle-aware scopes like lifecycleScope
and viewModelScope
, avoiding GlobalScope
, and following structured concurrency principles, developers can eliminate the risk of memory leaks and ensure smooth app performance. Always remember that coroutines should respect the lifecycle of Android components.
For more details on coroutines and structured concurrency, you can refer to the official Kotlin documentation: Kotlin Coroutines Documentation.