What is better Select vs Threads?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
select and threads are not two versions of the same tool. They solve different concurrency problems, so the better choice depends on whether your program is mostly waiting on I/O, how many concurrent activities you need, and how much coordination complexity you want to manage.
What select Is Actually Good At
select lets one thread wait on many file descriptors and react only when one becomes readable or writable. That makes it a strong fit for network servers, socket relays, and other I/O-heavy programs where most tasks spend their time waiting.
In modern Python, the selectors module is usually more convenient than calling select.select(...) directly:
One control loop can handle many connections without creating one thread per client.
What Threads Are Better At
Threads are often easier to understand when each task can behave like an independent worker. They are especially practical when you rely on blocking libraries or need a small number of background jobs.
This model is straightforward because each thread can block, sleep, and keep local state without participating in one central event loop.
Match The Model To The Workload
If the program is mostly waiting on many sockets or pipes, select-style concurrency usually scales better than creating large numbers of mostly idle threads. Memory overhead stays lower, and you avoid constant context switching between sleeping workers.
If the program depends on blocking APIs, older libraries, or a one-worker-one-job mental model, threads are often more practical. They are especially useful when the number of concurrent tasks is modest and the code benefits from simpler control flow.
The comparison changes again for CPU-bound work. In CPython, threads do not provide strong parallel speedups for CPU-heavy Python code because of the GIL. In that case, the better comparison is often threads versus multiprocessing, not select versus threads.
Complexity Matters As Much As Raw Performance
select centralizes I/O handling, which can scale well, but stateful workflows become harder to structure if everything must be modeled as callbacks or event-loop state. Threaded code can feel more natural, but shared mutable state introduces locks, races, and shutdown problems.
That tradeoff matters in real codebases. A slightly less efficient model that your team can debug confidently is often the better engineering choice.
Many systems also mix approaches. A program may use an event loop for socket readiness and a small thread pool for blocking file operations or library calls that do not fit non-blocking I/O cleanly.
In Modern Python, Also Consider Higher-Level APIs
In practice, most new Python code does not choose directly between raw select and raw threads. It often chooses between higher-level tools built on the same ideas:
- '
asyncioorselectorsfor event-driven I/O' - '
threadingorconcurrent.futures.ThreadPoolExecutorfor blocking tasks' - '
multiprocessingorProcessPoolExecutorfor CPU-heavy work'
That framing is more useful than asking which primitive is "better" in the abstract.
Common Pitfalls
- Asking the question without separating I/O-bound work from CPU-bound work.
- Using one thread per socket when thousands of mostly idle connections are expected.
- Forcing everything into one event loop even when important libraries are blocking.
- Ignoring synchronization costs when several threads mutate shared state.
- Comparing raw
selectwith threads while overlooking higher-level tools such asasyncioand executors.
Summary
- '
selectis strong for handling many concurrent I/O sources in one loop.' - Threads are strong when tasks are independent and blocking code is easier to work with.
- For CPU-bound Python code, processes are often the real alternative.
- Pick the model that matches the workload, library constraints, and complexity your team can support.

