Job lifecycle and cancellation in Kotlin - Time & Space Complexity
When working with Kotlin coroutines, understanding how job lifecycle and cancellation affect performance is important.
We want to know how the time cost changes as more jobs are created and cancelled.
Analyze the time complexity of the following code snippet.
val jobs = mutableListOf()
repeat(n) { i ->
val job = GlobalScope.launch {
delay(1000L)
println("Job $i done")
}
jobs.add(job)
}
jobs.forEach { it.cancel() }
This code launches n coroutines (jobs) and then cancels all of them.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Launching n jobs in a loop and then cancelling n jobs in another loop.
- How many times: Each loop runs n times, so launching and cancelling happen n times each.
As the number of jobs n increases, the total operations grow roughly in proportion to n.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 20 operations (10 launches + 10 cancels) |
| 100 | About 200 operations |
| 1000 | About 2000 operations |
Pattern observation: The total work grows linearly as n grows.
Time Complexity: O(n)
This means the time to launch and cancel jobs grows directly with the number of jobs.
[X] Wrong: "Cancelling all jobs happens instantly no matter how many jobs there are."
[OK] Correct: Each job must be individually cancelled, so cancelling takes time proportional to the number of jobs.
Understanding how job lifecycle operations scale helps you write efficient concurrent code and explain your reasoning clearly in interviews.
"What if we cancelled jobs one by one inside the launch block instead of after launching all? How would the time complexity change?"
Practice
Job?Solution
Step 1: Understand Job lifecycle states
A Kotlin Job begins in an active state, meaning it is running or ready to run.Step 2: Recognize cancellation and completion
A Job can be cancelled to stop its work, and after finishing or cancellation, it reaches a completed state.Final Answer:
A job starts active, can be cancelled, and then completes. -> Option CQuick Check:
Job lifecycle = active -> cancelled -> completed [OK]
- Thinking jobs start cancelled
- Assuming jobs never complete
- Confusing job states order
Solution
Step 1: Identify cancellation with waiting
The methodcancelAndJoin()cancels the job and suspends until it finishes.Step 2: Differentiate from other methods
cancel()only requests cancellation without waiting;join()waits but doesn't cancel;start()begins a job.Final Answer:
job.cancelAndJoin() -> Option AQuick Check:
Cancel and wait = cancelAndJoin() [OK]
- Using cancel() without waiting
- Confusing join() with cancel
- Calling start() to cancel
val job = CoroutineScope(Dispatchers.Default).launch {
repeat(3) { i ->
println("Working on $i...")
delay(100)
}
}
job.cancel()
Thread.sleep(500)Solution
Step 1: Analyze job launch and cancellation
The job starts and prints "Working on 0...", then delays. The cancel() is called immediately after launch.Step 2: Understand cancellation effect
Because cancellation happens quickly, some iterations may run before the job stops, so partial output appears.Final Answer:
Prints some "Working on" lines before stopping -> Option BQuick Check:
Partial output due to quick cancel [OK]
- Expecting all lines printed
- Expecting no output at all
- Assuming exception is printed
val job = CoroutineScope(Dispatchers.Default).launch {
try {
repeat(5) {
println("Working...")
delay(100)
}
} catch (e: Exception) {
println("Cancelled")
}
}
job.cancelAndJoin()Solution
Step 1: Understand cancellation exception type
Cancellation throwsCancellationException, which is a subclass ofThrowablebut notException.Step 2: Recognize catch block limitation
SinceCancellationExceptionis not caught bycatch (e: Exception), the cancellation is not handled as intended.Final Answer:
Catching Exception does not handle cancellation properly -> Option DQuick Check:
Catch CancellationException, not Exception [OK]
- Catching Exception instead of CancellationException
- Calling cancelAndJoin() incorrectly
- Thinking delay() is invalid inside launch
Solution
Step 1: Understand safe cancellation
UsingcancelAndJoin()cancels the job and waits until it fully stops, ensuring no work is left running.Step 2: Perform cleanup after cancellation
AftercancelAndJoin()returns, it's safe to run cleanup code because the job is fully cancelled.Final Answer:
Use job.cancelAndJoin() then run cleanup code after it returns. -> Option AQuick Check:
Cancel and wait before cleanup = cancelAndJoin() [OK]
- Starting cleanup before job stops
- Ignoring cancellation
- Using join() without cancelling
Ready to prove it?
Pass the test and this pattern moves unknown
