Skip to main content

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 Thread

Coroutine

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 Coroutine

Thread and Coroutine compared

AspectThreadCoroutine
Scheduled byThe operating system kernelThe program: an event loop or a runtime scheduler
SwitchingPreemptive: can be interrupted at any instructionCooperative: only at pause points such as await or yield
Memory eachIts own stack, often a megabyte or more reservedA small object holding the state it needs between pauses
How manyHundreds or thousands before memory and switching costs biteThousands to millions on a handful of threads
ParallelismRun in parallel on several CPU coresTake turns on one thread; parallel only when spread over several
Shared dataNeeds locks, since a switch can happen mid-updateSafe between pauses; state can still change across an await
Best forCPU-heavy work and blocking calls with no async versionMany concurrent waits: network calls, timers, streams
Examplesthreading.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)

Threadpython
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 total
Coroutinepython
import 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 total

Readers 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.

More

Settings