Side by side
ThreadvsCoroutine
What is the difference between a thread and a coroutine?
Updated 3 min read8 differences
In short
The operating system schedules threads and can interrupt them at any moment; coroutines pause only where they choose, so thousands can share one thread.
Thread
A thread is the smallest unit of execution an operating system can schedule, running inside a process and sharing that process's memory with other threads.
Read the page on ThreadCoroutine
A coroutine is a function that can pause partway, hand control back, and later resume where it stopped, so many tasks can take turns on just a few threads.
Read the page on CoroutineThread and Coroutine compared
| Aspect | Thread | Coroutine |
|---|---|---|
| Scheduled by | The operating system kernel | The program: an event loop or a runtime scheduler |
| Switching | Preemptive: can be interrupted at any instruction | Cooperative: only at pause points such as await or yield |
| Memory each | Its own stack, often a megabyte or more reserved | A small object holding the state it needs between pauses |
| How many | Hundreds or thousands before memory and switching costs bite | Thousands to millions on a handful of threads |
| Parallelism | Run in parallel on several CPU cores | Take turns on one thread; parallel only when spread over several |
| Shared data | Needs locks, since a switch can happen mid-update | Safe between pauses; state can still change across an await |
| Best for | CPU-heavy work and blocking calls with no async version | Many concurrent waits: network calls, timers, streams |
| Examples | threading.Thread in Python, Thread in Java, std::thread in C++ | Python async def, Kotlin suspend, C++20 co_await, Lua coroutine.yield |
The difference, explained
A thread is a unit of execution that the operating system schedules: it has its own stack and registers, shares its process's memory with the other threads, and can run in parallel with them on a multi-core CPU. A coroutine is a function that can pause partway, at points it marks itself, and later resume with its local variables intact. Coroutines run on top of threads: a scheduler inside the program, often an event loop, decides which ready coroutine runs next.
The essential difference is who decides when to switch. Threads are scheduled preemptively: the operating system can stop one at any instruction and run another, which is why shared data needs locks. Coroutines use cooperative multitasking: each runs until it reaches a pause point such as await or yield, so a switch saves only a little state and needs no help from the operating system. A thread reserves its own stack, often a megabyte or more, while a coroutine keeps just the state it needs between pauses, so a program that manages thousands of threads can manage millions of coroutines.
They are less rivals than layers. Coroutines suit I/O-bound work, such as a server holding thousands of network connections that spend most of their time waiting; threads suit CPU-bound work that should use several cores, and blocking calls that would otherwise stall a coroutine's thread. Many runtimes combine the two: Kotlin dispatches coroutines onto thread pools, Python's asyncio hands blocking calls to a thread with asyncio.to_thread, and Java's virtual threads, final since Java 21, bring the same cheap switching to code written with the ordinary thread API.
A common misconception is that coroutines run code in parallel. On one thread they only take turns, so a coroutine doing long CPU work without pausing blocks every other coroutine on that thread. Another is that cooperative switching removes every race: code between two pauses can't be interrupted, but shared state can still change across an await. Go's goroutines, despite the name, behave more like lightweight threads, since the runtime can preempt them and runs them in parallel across cores.
Which one should you use?
Choose Thread when…
- The work is CPU-bound and should use several cores.
- You call blocking libraries that have no asynchronous version.
- A long task must not freeze the others, even if it never pauses.
Choose Coroutine when…
- The program spends most of its time waiting on the network, the disk or timers.
- You need thousands of concurrent tasks, such as open connections.
- You want switches to happen only at known points, so less code needs locks.
- Your language or framework is built around async/await, as with Python's asyncio or Kotlin.
Three one-second waits, with threads and with coroutines (Python)
import threading, time
def download(name):
time.sleep(1) # blocks this thread; the OS runs the others
print(f"{name}: done")
threads = [threading.Thread(target=download, args=(n,)) for n in "abc"]
for t in threads: t.start()
for t in threads: t.join() # 3 threads, about 1 second in totalimport asyncio
async def download(name):
await asyncio.sleep(1) # pauses here; the event loop runs the others
print(f"{name}: done")
async def main():
await asyncio.gather(*(download(n) for n in "abc"))
asyncio.run(main()) # 1 thread, about 1 second in totalReaders ask
Are coroutines faster than threads?
For many concurrent waits, yes: creating and switching coroutines is far cheaper, and each one uses much less memory. For CPU-heavy work they are no faster, because coroutines on one thread don't run in parallel.
Can coroutines run on more than one thread?
Yes. Many runtimes spread coroutines over a pool of threads; Kotlin, for example, runs them on dispatchers backed by thread pools. A single coroutine still runs on one thread at a time, though it may resume on a different one after a pause.
Are goroutines threads or coroutines?
Something in between. Goroutines are lightweight threads managed by the Go runtime: cheap to create like coroutines, but the runtime can preempt them and runs them in parallel across cores, so shared data still needs synchronization.