Introduction to coroutines.
Threads work, but they are heavy. What if we could have the benefits of concurrency without the cost? Kotlin coroutines offer exactly this: lightweight concurrency that scales. Today we meet coroutines and understand what makes them different.
Threads are expensive. Each thread needs a stack. Context switching has overhead.
If you have thousands of concurrent tasks, thousands of threads consume gigabytes of memory. The system slows down.
Blocking is wasteful. A thread waiting for network does nothing. It holds resources while idle.
Modern applications need more concurrency than threads can provide efficiently. Web servers handle thousands of connections. Mobile apps perform many background operations.
We need something lighter.
A coroutine is a suspendable computation.
Like a thread, it executes a sequence of code. Unlike a thread, it can suspend without blocking the underlying thread.
When a coroutine suspends, the thread is free. It can run other coroutines. When the suspension ends, the coroutine resumes, possibly on a different thread.
Coroutines are cheap. Thousands, even millions, cost little. You can have a coroutine per request, per user action, per concurrent operation.
Think of coroutines as lightweight threads, but technically they are different. They cooperate rather than being preempted.
Threads are OS-managed. The OS schedules them. The OS context-switches them.
Coroutines are library-managed. Kotlin's coroutine library suspends and resumes them. No OS involvement for suspension.
Threads block. When a thread waits, it holds the thread. The thread cannot do other work.
Coroutines suspend. When a coroutine waits, the thread is released. The thread runs other coroutines.
This is the key difference. Suspending releases resources. Blocking holds them.
You can have millions of coroutines on a few threads. You cannot have millions of threads.
Blocking ties up a thread. Thread dot sleep blocks. The thread does nothing.
Suspending frees the thread. delay suspends. The coroutine waits, but the thread works.
This matters for scalability. A server with one thread per connection blocks on I/O. With coroutines, one thread handles many connections, suspending while each waits for I/O.
Write sequential-looking code that does not block. Call a suspend function, wait for the result, continue. It looks like blocking but is not.
Suspending is cooperative. The coroutine decides when to suspend. It is not preempted like a thread.
launch starts a new coroutine.
launch brace doSomething close brace. The code in braces runs in a new coroutine. The launch returns immediately.
launch is fire-and-forget. It starts work but does not return a result.
The result of launch is a Job. You can use it to cancel or wait for the coroutine.
launch needs a scope. Coroutines do not run in isolation. They belong to a scope that manages their lifecycle.
async starts a coroutine that produces a result.
val deferred equals async brace computeSomething close brace. The code runs in a coroutine. The result will be available later.
To get the result, call await: val result equals deferred dot await.
await suspends until the result is ready. It does not block.
Use launch when you do not need a result. Use async when you do.
async plus await is like starting a background computation and collecting its result.
Every coroutine runs in a scope.
The scope tracks all coroutines launched in it. When the scope ends, its coroutines are cancelled.
This is structured concurrency. You cannot launch a coroutine that outlives its scope. No orphan coroutines.
runBlocking creates a scope that blocks until all its coroutines complete. Good for main functions and tests.
coroutineScope creates a scope that suspends until complete. Good for grouping related operations.
Scopes prevent leaks. If you forget to wait for a coroutine, the scope handles it.
A simple example: runBlocking brace, launch delay 1000 print "Task 1 done", launch delay 500 print "Task 2 done", print "Both launched", close brace, print "All complete".
runBlocking creates a scope. Two coroutines launch. They run concurrently. Task 2 finishes first because its delay is shorter. After both complete, the scope ends. The outer print executes.
Sequential-looking, concurrent execution.
Let us summarize.
Coroutines are lightweight, suspendable computations.
Coroutines suspend without blocking threads. This enables massive concurrency.
launch starts a coroutine without returning a result.
async starts a coroutine that produces a result. await retrieves it.
Coroutine scopes manage lifetime and prevent leaks.
Coroutines make concurrent code look sequential while scaling efficiently.