Java Multithreading and Concurrency Interview Questions for Experienced Developers (2026)
By Preeti Verma
If you have a few years of Java experience, expect at least one multithreading question. They are popular because they are hard to fake: a candidate who has only read definitions falls apart on the first follow-up, while someone who has debugged a race condition answers calmly.
This guide covers the concurrency questions that come up most often, the points a strong answer makes, and the follow-ups to be ready for.
1. What is the difference between a process and a thread?
A process is an independent program with its own memory. Threads live inside a process and share its heap, which makes them cheap to create and fast to communicate between, but also means they can corrupt each other's data if you are careless. Say that sharing is both the benefit and the danger, and you have set up every question that follows.
2. How do you create a thread, and which way do you prefer?
You can extend Thread, implement Runnable, or submit a Callable to an executor. The preferred answer: implement Runnable or Callable and give it to an ExecutorService, because it separates the task from how it is run, lets you reuse threads, and lets Callable return a value and throw checked exceptions.
3. synchronized vs volatile
This is the single most asked concurrency question.
- synchronized gives mutual exclusion (only one thread in the block at a time) and visibility (changes made inside are visible to the next thread that takes the same lock).
- volatile gives visibility and ordering for one variable, but not atomicity.
The follow-up is almost always: "Is count++ safe on a volatile int?" No. It is a read, an add and a write, and two threads can interleave them. Use synchronized, an AtomicInteger or a lock instead.
4. What is a race condition? Show one.
A race condition is when the result depends on the timing of threads. The classic example is a check-then-act: two threads both see that an account has enough balance, both withdraw, and the balance goes negative. The fix is to make the check and the action one atomic step, using a lock, an atomic compare-and-set, or a database constraint.
5. What are the four conditions for deadlock, and how do you avoid it?
Deadlock needs all four: mutual exclusion, hold and wait, no preemption, and circular wait. You prevent it by breaking one. The most practical is to always acquire locks in the same global order, which removes circular wait. Others: use tryLock with a timeout, hold fewer locks, or hold them for less time. Mention that you would find a deadlock in production with a thread dump.
6. wait/notify vs sleep
sleep pauses the current thread and keeps any lock it holds. wait must be called inside a synchronized block, releases the lock, and waits until another thread calls notify or notifyAll. Always call wait inside a while loop that rechecks the condition, because of spurious wakeups. In modern code you would usually use BlockingQueue or Condition instead of raw wait/notify.
7. Explain the ThreadPoolExecutor parameters
Know these: core pool size, maximum pool size, keep-alive time, the work queue and the rejection policy. The order matters and is a favourite trap: new tasks first fill the core threads, then go into the queue, and only when the queue is full does the pool grow towards the maximum. An unbounded queue means the pool never grows past the core size.
Then say why Executors.newFixedThreadPool and newCachedThreadPool are risky in production: the first uses an unbounded queue (memory can grow without limit), the second can create an unbounded number of threads. Prefer building a ThreadPoolExecutor with a bounded queue and an explicit rejection policy.
8. What is the difference between submit and execute?
execute takes a Runnable and returns nothing. submit takes a Runnable or Callable and returns a Future. An exception thrown in an executed task goes to the thread's uncaught handler, while in a submitted task it is captured in the Future and thrown only when you call get(). People forget the second half.
9. CompletableFuture: how would you call three services and combine the results?
CompletableFuture<A> a = CompletableFuture.supplyAsync(() -> callA(), pool);
CompletableFuture<B> b = CompletableFuture.supplyAsync(() -> callB(), pool);
CompletableFuture<C> c = CompletableFuture.supplyAsync(() -> callC(), pool);
CompletableFuture.allOf(a, b, c)
.orTimeout(2, TimeUnit.SECONDS)
.thenApply(v -> combine(a.join(), b.join(), c.join()));
A strong answer also says: pass your own executor (the default is the shared common pool), add a timeout, handle failure with exceptionally or handle, and avoid calling get() on the request thread, which throws away the benefit.
10. What is ThreadLocal, and what can go wrong?
ThreadLocal gives each thread its own copy of a variable, often used for user context or date formatters. The danger is in thread pools: threads are reused, so a value you do not remove can leak into the next request. Always call remove() in a finally block.
11. Atomic classes and compare-and-set
AtomicInteger, AtomicLong and AtomicReference use a hardware-level compare-and-set to update a value without a lock. Say that they are lock-free and fast under low to moderate contention, and mention that under very high contention LongAdder often performs better for counters.
12. ReentrantLock vs synchronized
ReentrantLock adds tryLock, timed locking, interruptible locking, fairness and multiple Conditions. The cost is that you must call unlock() in a finally block yourself. Use synchronized by default for its simplicity and reach for ReentrantLock when you need those extras.
13. Virtual threads (Java 21)
Virtual threads are lightweight threads managed by the JVM, designed so you can write simple blocking code and still handle very many concurrent tasks. They suit I/O-bound work such as handling many requests that mostly wait on a database or network call. They do not make CPU-bound work faster. Say you would not pool them, because they are cheap to create, and that you would watch for pinning when blocking inside synchronized code on older versions. Interviewers use this to see whether you keep up with the platform.
Scenario questions
- "Two users update the same account balance and one update disappears. How, and how do you prevent it?"
- "Your service calls three downstream APIs and one is slow. How do you stop it from taking everything down?"
- "A thread pool is full and requests are being rejected. What do you check?"
For each, explain the cause first, then compare two or three options honestly with their trade-offs. That is what separates a senior answer from a textbook one. See also HashMap vs ConcurrentHashMap explained simply for the thread-safe collections question that usually comes next.
How to practise
Write a tiny program that shows a race condition, then fix it three ways. Seeing the bug fail makes the explanation stick. Then practise saying the answers out loud: our Resources section has Core Java scenarios with a free preview, and a mock interview will ask the follow-up questions for you.
FAQ
How deep do I need to go on the Java Memory Model? Know the idea of visibility and "happens-before" in plain words: a write by one thread is guaranteed to be seen by another only if there is a proper link between them, such as a lock, a volatile variable or a thread start or join.
Do I need to know the internals of ConcurrentHashMap? At this level, know what it guarantees and why it does not allow null keys or values. Deeper detail is a bonus.
The takeaway
Be clear on synchronized vs volatile, how thread pools really behave, how deadlock happens and is avoided, and how to combine async calls safely. Explain with a small example each time, and you will handle most concurrency rounds.