FixedThreadPool vs CachedThreadPool vs SingleThreadExecutor — When to Use Each (and When Not To)
Fast recall
Quick revision
Core answer
Executors factory and wrap a ThreadPoolExecutor with different presets. `newFixedThreadPool(N)` has N fixed threads with an unbounded `LinkedBlockingQueue` — safe thread count, but the queue can grow forever and OOM you. `newCachedThreadPool()` has unbounded max threads (Integer.MAX_VALUE) with a SynchronousQueue that hands off tasks to new threads — a spike can spawn tens of thousands of threads and crash the JVM. `newSingleThreadExecutor()` has exactly one thread with an unbounded queue — same OOM risk as fixed. In production, avoid all three factories (explicitly banned in the Alibaba Java Style Guide) and create a ThreadPoolExecutor directly with a bounded queue and a rejection policy. The factories are fine for demos, scripts, and tests — never for long-running services.Whether the candidate knows the hidden risks of each Executors factory — not just 'when to use each' but 'why production code avoids them'
Recommending `newCachedThreadPool()` for 'flexible scaling' without realizing it's unbounded. Not knowing `newFixedThreadPool` uses an unbounded queue
Mention the Alibaba Java Style Guide's explicit ban, the `SynchronousQueue` mechanism behind cached pools, and that the right answer is `new ThreadPoolExecutor(...)` directly with bounded queue and rejection policy
Interview-ready guide
Interview answer
All three come from the Executors class, and all three have hidden defaults that make them unsafe for production.
`newFixedThreadPool(N)`
- N core and max threads, threads never time out
- Unbounded `LinkedBlockingQueue`
- Safe thread count but dangerous queue — under sustained overload, queue grows forever, holding references to task state, eventually causing heap OOM
- OK for demos where you control the producer rate
`newCachedThreadPool()`
- Core = 0, max = Integer.MAX_VALUE,
SynchronousQueue - Synchronous queue can't hold tasks — each submission directly hands off to a thread or creates one
- A spike of 50,000 tasks = 50,000 threads × ~1 MB stack = 50 GB — instant
OutOfMemoryError: unable to create new native thread - Looks elastic, but the elasticity has no ceiling
- Alibaba Java Style Guide explicitly bans it
`newSingleThreadExecutor()`
- One thread, unbounded
LinkedBlockingQueue - Same OOM risk as fixed if producers outrun the single consumer
- Useful for serializing access to non-thread-safe resources
- Still needs a bounded queue in production
What they all share
- Fine for demos, tests, CLI tools
- Dangerous for long-running services that handle external traffic
The production answer
- Construct `ThreadPoolExecutor` directly with explicit parameters:
- Core and max thread counts
- Keep-alive time
- Bounded queue like
ArrayBlockingQueue(1000) - Named thread factory for thread dumps
- Rejection policy like
CallerRunsPolicyfor backpressure
- Three lines of code, but reliability you can predict
On Java 21+
- For I/O-bound work, use `Executors.newVirtualThreadPerTaskExecutor()`
- Pair with a `Semaphore` to cap downstream resource usage (DB connections, HTTP client pool)
- Virtual threads solve the creation-cost problem but NOT the bounded-concurrency problem
Recommendation: If you see Executors.newFixedThreadPool or Executors.newCachedThreadPool in production code, rewrite it as new ThreadPoolExecutor(core, max, keepAlive, unit, boundedQueue, threadFactory, rejectedHandler). The Alibaba Java Style Guide's rule to avoid Executors.newXxx factories exists for good reason — multiple high-profile outages have been traced to exactly this pattern. Treat these factories as "teaching tools, not production tools," and invest the 3 extra lines to configure the pool explicitly.
`FixedThreadPool` vs `CachedThreadPool` vs `SingleThreadExecutor` — When to Use Each (and When Not To)
Direct Answer
All three come from the Executors factory and wrap a ThreadPoolExecutor with different presets. newFixedThreadPool(N) has N fixed threads with an unbounded LinkedBlockingQueue — safe thread count, but the queue can grow forever and OOM you. newCachedThreadPool() has unbounded max threads (Integer.MAX_VALUE) with a SynchronousQueue that hands off tasks to new threads — a spike can spawn tens of thousands of threads and crash the JVM. newSingleThreadExecutor() has exactly one thread with an unbounded queue — same OOM risk as fixed. In production, avoid all three factories (explicitly banned in the Alibaba Java Style Guide) and create a ThreadPoolExecutor directly with a bounded queue and a rejection policy. The factories are fine for demos, scripts, and tests — never for long-running services.
Common Mistakes & Pitfalls
Recommending newCachedThreadPool() for 'flexible scaling' without realizing it's unbounded. Not knowing newFixedThreadPool uses an unbounded queue
Check your understanding
Answer these without looking back at the article.
- Why does `newCachedThreadPool` use `SynchronousQueue` instead of a normal queue?
- What happens when a `CallerRunsPolicy` rejection fires?
- How do virtual threads on Java 21 change this analysis?
- Why can a fixed thread pool still cause OOM even with a fixed thread count?
- What's `ScheduledThreadPoolExecutor` and what are its pitfalls?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.