Concurrency
Multithreading
Asynchronous Programming
Software Development
Performance Optimization

Threads vs. Async

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Threads and async are both ways to deal with multiple tasks that overlap in time, but they solve different problems. Threads are about letting multiple execution contexts progress independently, often on different CPU cores. Async is about structuring code so that waiting operations do not block a thread unnecessarily.

What Threads Give You

A thread is an operating-system scheduled execution path inside a process. Multiple threads can run concurrently, and on multi-core hardware they may run in parallel.

Threads are useful when:

  • work is CPU-bound and parallelizable
  • a library API is blocking and cannot be made async
  • separate units of execution need independent stacks and scheduling

A simple Python example:

python
1import threading
2import time
3
4
5def worker(name):
6    print(f"{name} starting")
7    time.sleep(1)
8    print(f"{name} done")
9
10
11threads = [threading.Thread(target=worker, args=(f"t{i}",)) for i in range(3)]
12for t in threads:
13    t.start()
14for t in threads:
15    t.join()

The key point is that each thread is a separate execution context.

What Async Gives You

Async programming is usually built around an event loop. Instead of blocking while waiting for I/O, the task yields control so other work can run.

Async is especially strong for:

  • network requests
  • database calls
  • file I/O with async-compatible libraries
  • high-concurrency servers handling many waiting tasks

Example with Python asyncio:

python
1import asyncio
2
3
4async def worker(name):
5    print(f"{name} starting")
6    await asyncio.sleep(1)
7    print(f"{name} done")
8
9
10async def main():
11    await asyncio.gather(*(worker(f"a{i}") for i in range(3)))
12
13
14asyncio.run(main())

Only one thread may be involved here, but the program still overlaps waiting time efficiently.

The Real Difference

The most important difference is this:

  • threads let multiple execution contexts exist at once
  • async lets one thread handle many waiting tasks efficiently

That means threads are not "better async," and async is not "lightweight threading." They are different tools.

CPU-Bound Versus I/O-Bound Work

A common rule of thumb is:

  • use async for I/O-bound waiting-heavy workloads
  • use threads or processes for CPU-bound workloads

Why? Because async does not make CPU-heavy computation disappear. If one async task spends a long time doing pure computation without yielding, it blocks the event loop.

Likewise, threads are often wasteful for large numbers of tiny network waits because each thread has scheduling and memory overhead.

You Can Combine Them

Real systems often combine both models. For example, an async web service may use async networking for requests and then offload blocking or CPU-heavy work to threads or processes.

That is a healthy design because the tools are complementary, not mutually exclusive.

Error Handling and Complexity

Threads introduce risks such as:

  • race conditions
  • deadlocks
  • shared-state bugs

Async code avoids some shared-state problems when it stays single-threaded, but it introduces its own complexity around:

  • cancellation
  • event-loop boundaries
  • accidentally blocking the loop
  • mixing sync and async libraries incorrectly

So neither approach is automatically "simpler." Simplicity depends on whether the model matches the workload.

Common Pitfalls

The biggest mistake is using async for CPU-heavy work and expecting speedups. Async is about waiting efficiently, not about making computation parallel.

Another mistake is creating threads for huge numbers of mostly idle I/O tasks. That often scales worse than async event-loop handling.

People also compare the two as if only one should exist in a codebase. Many systems use async for I/O and threads or processes for other workloads.

Finally, do not block the async event loop with long synchronous calls. That turns async code back into effectively serial code.

Summary

  • Threads and async both deal with overlapping tasks, but in different ways.
  • Threads are useful for parallel execution and for blocking APIs.
  • Async is ideal for I/O-bound workloads with lots of waiting.
  • CPU-bound work usually needs threads or processes, not only async.
  • The best systems often combine both models instead of treating them as rivals.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.