Coroutines Intro
Kotlin is a modern, concise language whose coroutines let you write asynchronous, non-blocking code that reads like ordinary sequential code — no callback pyramids required.
Learn Coroutines Intro in our free Kotlin course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Kotlin course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll write suspend functions, launch coroutines, and run work concurrently with async / await .
What You'll Learn in This Lesson
1️⃣ suspend Functions and runBlocking
A suspend function can pause at suspension points (like delay ) without blocking its thread. You can only call one from another suspend function or a coroutine builder. runBlocking bridges ordinary code into the coroutine world — common in main and tests.
The code reads top-to-bottom like normal sequential logic, yet the delay doesn't freeze the thread — that's the magic of coroutines.
2️⃣ launch, async, and Concurrency
launch starts a fire-and-forget coroutine and returns a Job . async starts one that produces a result as a Deferred , which you read with await() . Two async calls run concurrently .
Both loads each take a second, but because they ran concurrently the total wait is about one second, not two. That's how coroutines turn sequential waits into parallel ones.
Your turn. Replace the TODO , then run and compare.
Mini-Challenge: Concurrent Loads
Run two suspend functions concurrently with async and combine their results.
Common Errors (and the fix)
- "Suspend function can only be called from a coroutine" — Call it inside runBlocking , launch , or another suspend function.
- Using Thread.sleep — That blocks the thread. Use delay inside coroutines instead.
- Awaiting sequentially by mistake — Calling async { ... }.await() back-to-back is sequential. Start both first, then await, for concurrency.
- Forgetting to import — Add import kotlinx.coroutines.* and the coroutines dependency.
Pro Tips
- 💡 Structured concurrency: child coroutines launched in a scope are cancelled together — leaks are prevented by design.
- 💡 coroutineScope waits for all its children, a cleaner alternative to manual join() .
- 💡 Dispatchers choose the thread pool: Dispatchers.IO for network/disk, Default for CPU work.
📋 Quick Reference — Coroutines
Concept
Kotlin Syntax
Suspend function
suspend fun load()
Non-blocking wait
delay(1000)
Bridge into coroutines
runBlocking { ... }
Fire-and-forget
launch { ... }
Concurrent result
async { ... }
Get the result
deferred.await()
Frequently Asked Questions
🎉 Lesson Complete!
- ✅ Coroutines are lightweight, suspendable units of work
- ✅ suspend marks functions that can pause without blocking
- ✅ delay is non-blocking; Thread.sleep is not
- ✅ launch is fire-and-forget; async / await return results
- ✅ Concurrent async calls run in parallel
- ✅ Next lesson: Exception Handling
Practice quiz
What does the 'suspend' keyword mark a function as?
- A function that can pause at suspension points without blocking its thread
- A function that always runs on a new thread
- A function that cannot be cancelled
- A function that blocks the main thread
Answer: A function that can pause at suspension points without blocking its thread. suspend marks a function that may pause and resume later without blocking the underlying thread.
Where can a suspend function be called from?
- Any ordinary function
- Only the main function
- Another suspend function or a coroutine builder like launch/async/runBlocking
- Only from inside a class
Answer: Another suspend function or a coroutine builder like launch/async/runBlocking. Suspend functions can only be called from another suspend function or a coroutine builder.
How does delay() differ from Thread.sleep()?
- delay() is slower than Thread.sleep()
- They are identical
- delay() blocks two threads at once
- delay() suspends the coroutine and frees the thread; Thread.sleep blocks the thread
Answer: delay() suspends the coroutine and frees the thread; Thread.sleep blocks the thread. delay suspends only the current coroutine, releasing the thread; Thread.sleep blocks the whole thread.
What does the launch coroutine builder return?
- A Job
- A Deferred
- A String
- Nothing at all
Answer: A Job. launch starts fire-and-forget work and returns a Job you can join or cancel.
What does async return?
- A Job
- A Deferred that you read with await()
- A plain value immediately
- A Thread
Answer: A Deferred that you read with await(). async returns a Deferred<T>; call await() to get the result.
What is runBlocking typically used for?
- Cancelling all coroutines
- Replacing the JVM thread pool
- Bridging ordinary code into the coroutine world, e.g. in main and tests
- Creating new threads only
Answer: Bridging ordinary code into the coroutine world, e.g. in main and tests. runBlocking bridges normal blocking code and the coroutine world.
How do you get the result out of a Deferred?
- By calling .join()
- By calling .await()
- By calling .close()
- By reading .value directly
Answer: By calling .await(). await() suspends until the Deferred's result is ready and returns it.
Starting two async blocks and then awaiting both lets the work run...
- Sequentially, one after the other
- On the UI thread only
- Never, it deadlocks
- Concurrently, so total wait is about one delay not two
Answer: Concurrently, so total wait is about one delay not two. Both async coroutines run concurrently, so two 1s tasks finish in about 1s total.
What does structured concurrency guarantee about child coroutines in a scope?
- They are cancelled together with the scope
- They run on separate processes
- They never get cancelled
- They ignore their parent scope
Answer: They are cancelled together with the scope. Children launched in a scope are cancelled together, preventing leaks by design.
Which Dispatcher is intended for network and disk work?
- Dispatchers.Main
- Dispatchers.Default
- Dispatchers.IO
- Dispatchers.Unconfined only
Answer: Dispatchers.IO. Dispatchers.IO is for blocking I/O like network and disk; Default is for CPU work.
Continue this course
- Previous: Scope Functions (let, run, apply, also, with)
- Next: Exception Handling