Top 28 OS Threads and Concurrency Interview Questions (2026)

What changed in 2026 drives
Mass-recruiter offer letters are flatter for 2026 batch - the 4-5 LPA ASE band has barely budged in three years while inflation eats real wages. Premium tracks (Digital, Pro, Elite, Specialist) are still where the differential lives, and they are entirely test-driven. If you are aiming higher than the default offer, the coding round is not optional pageantry - it is the entire interview.
What I'd actually study for this
- 01Two solid coding-round answers (1 medium-hard DSA each, with edge-case discussion) > five half-baked ones
- 02One real project you can defend end-to-end - file paths, design decisions, and what you would change
- 03One DBMS schema you actually built (not a textbook ER diagram), with at least 3 join-heavy queries written from memory
- 04Three behavioural STAR stories: failure recovered, conflict handled, ownership taken
Where most candidates trip up
The single biggest mistake is treating company-specific guides as primary prep and DSA as secondary. It is the opposite. Mass recruiters use the test as a filter, but premium tracks at every IT services company use coding to allocate offer band. Spend 70% of prep time on DSA + system fundamentals, 20% on company-specific patterns, 10% on HR rehearsal. Reverse that ratio and you collect the default offer.
Editorial commentary by Aditya Sharma · written for PapersAdda · not generated, not aggregated.
Last Updated: June 2026 | Level: Beginner to Advanced | Format: Q&A with Code and Diagrams
Threads and concurrency are tested at every level of SDE interviews. Candidates report entry-level questions covering mutex vs semaphore and senior-level questions covering lock-free programming and memory models. Based on public preparation resources and candidate-reported interview feedback, the Producer-Consumer and Dining Philosophers problems with semaphore solutions appear in virtually every backend SDE-1 interview round. This guide covers 28 questions from the basics of threads through classic synchronization problems, with code examples in pseudocode and Java/C.
Table of Contents
- Thread Fundamentals (Q1-Q8)
- Synchronization Primitives (Q9-Q17)
- Classic Problems (Q18-Q23)
- Advanced Concurrency (Q24-Q28)
Thread Fundamentals
Q1. What is a thread? What does it share with other threads? Easy
A thread is the smallest unit of CPU execution within a process. Multiple threads within a process share:
Shared (per process):
├── Code segment (text)
├── Data segment (global and static variables)
├── Heap (dynamic memory from malloc/new)
├── Open file descriptors
├── Signals and signal handlers
└── Process ID (PID)
Private (per thread):
├── Thread ID (TID)
├── Program Counter (PC)
├── CPU registers
├── Stack (local variables, function call frames)
└── Thread-local storage (TLS)
Why share heap but have private stack? The stack holds local variables and function call frames for THAT thread's execution path. The heap holds dynamically-allocated objects accessible to any thread that holds a pointer.
Q2. What is the difference between concurrency and parallelism? Easy
| Aspect | Concurrency | Parallelism |
|---|---|---|
| Definition | Structuring a system to deal with multiple tasks that may overlap in time | Multiple tasks literally execute at the same instant |
| Hardware requirement | Works on a single CPU (time-multiplexed) | Requires multiple CPUs or cores |
| Goal | Correctness and responsiveness | Speed (throughput) |
| Example | A single-core CPU running a web server that handles multiple requests via context switching | A 4-core CPU running 4 threads that each sort a quarter of an array simultaneously |
Rob Pike's definition: "Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once."
A concurrent program may or may not run in parallel. Parallelism always requires multiple processors.
Q3. What is a user-level thread vs. a kernel-level thread? Medium
| Aspect | User-Level Thread (ULT) | Kernel-Level Thread (KLT) |
|---|---|---|
| Managed by | User-space library (e.g., GNU Pth, Java green threads) | OS kernel |
| Context switch | Fast (library code, no syscall) | Slower (kernel mode switch) |
| I/O blocking | One block = ALL threads in process block | Only the blocked thread blocks |
| Parallelism | None on multi-core (kernel sees 1 thread) | True parallelism (kernel schedules on multiple cores) |
| Example | Old Java virtual machine threads, Go goroutines (M:N model) | POSIX pthreads on Linux, Java threads since JDK 1.3 |
Thread models:
| Model | Description |
|---|---|
| 1:1 (kernel threading) | Each user thread = 1 kernel thread. Full parallelism, higher overhead. Used by Linux, Windows. |
| M:1 (user threading) | Many user threads multiplexed onto 1 kernel thread. No parallelism. |
| M:N (hybrid) | Many user threads mapped to fewer kernel threads. Complex. Used by Go (goroutines). |
Q4. How do you create and join threads in C using pthreads? Easy
#include <pthread.h>
#include <stdio.h>
void *thread_function(void *arg) {
int id = *(int *)arg;
printf("Thread %d running\n", id);
return NULL;
}
int main() {
pthread_t threads[3];
int ids[3] = {1, 2, 3};
// Create threads
for (int i = 0; i < 3; i++) {
pthread_create(&threads[i], NULL, thread_function, &ids[i]);
}
// Wait for threads to finish
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
printf("All threads done\n");
return 0;
}
Key functions:
pthread_create(): Creates a new thread.pthread_join(): Waits for thread to finish (like waitpid for processes).pthread_detach(): Thread cleans up itself; cannot be joined.pthread_exit(): Terminate calling thread.
Q5. What is a race condition? Give an example. Easy
A race condition occurs when the correctness of a program depends on the interleaving of thread operations, and some interleavings produce incorrect results.
Classic example: unsynchronized counter
int counter = 0;
// Thread T1 // Thread T2
counter++; counter++;
// counter++ is NOT atomic. It compiles to:
// 1. LOAD counter into register
// 2. ADD 1 to register
// 3. STORE register back to counter
// Race condition scenario:
T1: LOAD counter (reads 0)
T2: LOAD counter (reads 0) <- T2 runs before T1 stores
T1: ADD 1 (register = 1)
T2: ADD 1 (register = 1)
T1: STORE 1 -> counter = 1
T2: STORE 1 -> counter = 1 <- Expected: 2, Got: 1!
Detection: Race conditions are notoriously hard to find because they depend on timing (often fine in single-threaded tests, fail intermittently under load).
Fix: Use a mutex or atomic operation around the counter.
Q6. What is a critical section? What are the requirements for a valid critical section solution? Easy
A critical section is a code region where a process/thread accesses shared resources that must not be accessed by more than one thread at a time.
Requirements for a valid solution (Dijkstra's criteria):
| Requirement | Description |
|---|---|
| Mutual Exclusion | At most one process is in its critical section at any time |
| Progress | If no process is in the critical section and some want to enter, the decision on who enters cannot be postponed indefinitely (no deadlock on entry) |
| Bounded Waiting | After a process requests entry, there is a bound on how many times other processes can enter before it does (no starvation) |
Solution structure:
while (true) {
entry_section(); // Request permission to enter
critical_section(); // Access shared resources
exit_section(); // Signal that critical section is done
remainder_section(); // Rest of code
}
Q7. Explain Peterson's Algorithm for mutual exclusion. Medium
Peterson's Algorithm is a software-only solution for two processes (P0 and P1) that satisfies all three mutual exclusion requirements.
// Shared variables
bool flag[2] = {false, false};
int turn = 0;
// Process Pi (i = 0 or 1, j = 1 - i)
void process(int i) {
int j = 1 - i;
while (true) {
// Entry section
flag[i] = true; // "I want to enter"
turn = j; // "But you go first if you want"
while (flag[j] && turn == j) {
// busy wait
}
// CRITICAL SECTION
// Access shared resource
// Exit section
flag[i] = false; // "I'm done"
// Remainder section
}
}
Why it works:
- Mutual Exclusion: If both are in entry, one will busy-wait because turn is set to the other.
- Progress: If only one process wants in, it enters immediately.
- Bounded Waiting: After P0 sets turn=P1 and P1 is in its CS, P0 waits. When P1 exits, P1 sets flag[1]=false, allowing P0 in. P1 cannot re-enter ahead of P0 (turn stays set to P1 direction).
Limitation: Works only for two processes. Does not work correctly on modern CPUs without memory barriers due to instruction reordering.
Q8. What is the difference between a process and a thread from a scheduling perspective? Medium
| Aspect | Process | Thread |
|---|---|---|
| Scheduling unit | Yes (the process context) | Yes (kernel threads are independently scheduled) |
| Context switch cost | High (full address space, TLB flush) | Lower (shared address space, only register/stack swap) |
| Communication | IPC (pipes, sockets, shared memory, signals) | Shared memory directly (same address space) |
| Fault isolation | Strong (crash in P1 does not affect P2) | Weak (crash in one thread kills all threads in process) |
| Creation time | Slow (fork + copy-on-write setup) | Fast (new stack + register set only) |
| Typical switch time | 1-10 microseconds | 0.1-1 microsecond |
When to use processes vs. threads:
- Use processes when fault isolation is critical (web server workers, browser tabs, microservices).
- Use threads when low-overhead communication and fast context switching are needed (parallel algorithms, I/O multiplexing within a service).
Synchronization Primitives
Q9. What is a mutex? How does it work? Easy
A mutex (mutual exclusion lock) is a synchronization primitive that ensures only one thread can hold the lock at a time.
Key property: ownership. Only the thread that acquired the mutex can release it.
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;
void increment() {
pthread_mutex_lock(&mutex); // Acquire -- blocks if already held
shared_counter++; // Critical section
pthread_mutex_unlock(&mutex); // Release -- wakes waiting threads
}
Mutex operations:
lock(): If free, acquire and continue. If held by another thread, block.unlock(): Release lock, wake one waiting thread.trylock(): Attempt to acquire without blocking; return error if already held.
Reentrant mutex (recursive mutex): Can be locked multiple times by the SAME thread without deadlock. Each lock must be matched with an unlock.
Q10. What is a semaphore? Explain binary and counting semaphores. Easy
A semaphore is an integer variable with two atomic operations:
- wait() / P() / down(): Decrement; if result < 0, block.
- signal() / V() / up(): Increment; if any blocked, wake one.
Binary semaphore: Value is 0 or 1. Behaves like a mutex but with NO ownership (any thread can signal, not just the one that waited). Used for mutual exclusion.
Counting semaphore: Value can be any non-negative integer. Used to control access to a resource with N instances.
// Counting semaphore: control access to a pool of N resources
semaphore S = N; // N resources available
// Thread wanting to use a resource:
wait(S); // Decrement. If S becomes negative, block.
use_resource();
signal(S); // Increment. If any blocked, wake one.
Key difference from mutex:
| Mutex | Semaphore | |
|---|---|---|
| Ownership | Yes (only locker can unlock) | No (any thread can signal) |
| Value range | 0 or 1 | 0 to N |
| Primary use | Mutual exclusion | Both exclusion AND signaling |
Q11. What is a monitor? How is it different from a semaphore? Medium
A monitor is a high-level synchronization construct that encapsulates shared data, operations on that data, and the synchronization mechanism.
Properties:
- Only one thread can be active inside the monitor at a time (mutual exclusion enforced automatically).
- Condition variables inside the monitor allow threads to wait for specific conditions.
- Condition variable operations:
wait()andsignal()(ornotify()).
// Java synchronized block = monitor
public class BoundedBuffer {
private int[] buffer;
private int count = 0;
public synchronized void produce(int item) throws InterruptedException {
while (count == buffer.length) {
wait(); // Release lock and wait for space
}
buffer[count++] = item;
notifyAll(); // Wake consumers
}
public synchronized int consume() throws InterruptedException {
while (count == 0) {
wait(); // Release lock and wait for item
}
int item = buffer[--count];
notifyAll(); // Wake producers
return item;
}
}
Semaphore vs. Monitor:
| Semaphore | Monitor | |
|---|---|---|
| Abstraction level | Low-level | High-level |
| Error prone | Yes (easy to misuse wait/signal ordering) | No (compiler enforces structure) |
| Condition variables | No built-in | Yes |
| Language support | OS/library call | Java synchronized, Python with-statement |
Q12. What is a condition variable? How is it used with a mutex? Medium
A condition variable allows threads to wait for a specific condition to become true. It is ALWAYS used with a mutex.
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int data_ready = 0;
// Consumer thread
void *consumer(void *arg) {
pthread_mutex_lock(&mutex);
while (!data_ready) { // Check condition (while loop, not if)
pthread_cond_wait(&cond, &mutex); // Atomically: release mutex + sleep
// On wake: mutex is re-acquired before returning
}
consume_data();
pthread_mutex_unlock(&mutex);
return NULL;
}
// Producer thread
void *producer(void *arg) {
pthread_mutex_lock(&mutex);
produce_data();
data_ready = 1;
pthread_cond_signal(&cond); // Wake one waiting thread
pthread_mutex_unlock(&mutex);
return NULL;
}
Critical: use while loop, not if
Spurious wakeups are allowed by POSIX: a thread can wake up from cond_wait() without being signaled. The while loop re-checks the condition and goes back to sleep if it is still not met.
signal() vs broadcast():
signal(): Wake one waiting thread.broadcast(): Wake ALL waiting threads. Use when the condition change may allow multiple waiters to proceed.
Q13. What is the difference between spinlock and mutex? When to use each? Medium
| Aspect | Spinlock | Mutex |
|---|---|---|
| Waiting method | Busy-wait (spin in loop) | Block (sleep, context switch) |
| CPU usage while waiting | High (100% for that core) | Zero (thread sleeps) |
| Context switch | None while spinning | Required to sleep and wake |
| Good for | Very short critical sections, multi-core, no sleeping inside | Longer critical sections, any blocking inside |
| Kernel use | Interrupt handlers, very short kernel critical sections | User-space, longer OS critical sections |
Decision rule:
- Expected wait < cost of context switch (~1-10 microseconds): use spinlock.
- Expected wait > context switch cost: use mutex.
- Must sleep inside critical section (I/O wait, long computation): must use mutex (never sleep while holding a spinlock).
In practice: User-space applications almost always use mutexes. Spinlocks are used in OS kernels, device drivers, and lock-free data structures.
Q14. What is the ABA problem? Hard
The ABA problem is a subtle issue in lock-free programming using compare-and-swap (CAS).
CAS (Compare-And-Swap):
CAS(address, expected, new_value):
if *address == expected:
*address = new_value
return true
return false
(This entire operation is atomic)
ABA scenario (lock-free stack):
Initial stack: A -> B -> C
Thread T1 reads top = A (wants to pop A, CAS top from A to B)
T1 is interrupted. T2 runs:
T2 pops A (top = B)
T2 pops B (top = C)
T2 pushes A back (top = A -> C, B is freed)
T1 resumes:
T1 CAS(top, A, B): top IS A, so CAS succeeds.
T1 sets top = B. But B is FREED memory!
Heap corruption or crash.
Why it is a problem: CAS sees "A" twice and thinks nothing changed. But the state changed from A->B->C to A->C (B was removed and re-added A). The pointer looks the same but the underlying state is different.
Solution: Tagged/versioned pointers. Include a version counter with each pointer:
CAS on <pointer, version> pair.
Each modification increments version.
T2's operations: <A,1> -> <B,2> -> <C,3> -> <A,4>
T1's CAS: expected = <A,1>, actual = <A,4>. CAS FAILS. T1 retries.
In Java: AtomicStampedReference<V> provides tagged references.
Q15. What is a read-write lock? When is it useful? Medium
A read-write lock (rwlock) allows concurrent reading but exclusive writing:
- Multiple threads can hold the read lock simultaneously.
- Only one thread can hold the write lock, and no readers can hold the lock while a writer does.
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int shared_data = 0;
// Reader
void reader() {
pthread_rwlock_rdlock(&rwlock); // Multiple readers allowed simultaneously
int val = shared_data;
pthread_rwlock_unlock(&rwlock);
}
// Writer
void writer(int new_val) {
pthread_rwlock_wrlock(&rwlock); // Exclusive access
shared_data = new_val;
pthread_rwlock_unlock(&rwlock);
}
When to use:
- Data is read far more often than written (e.g., in-memory cache, configuration, routing tables).
- Read operation is non-trivial (long enough that contention with other readers matters).
Trade-off: More complex than a mutex. Writers can starve if readers arrive continuously (depends on implementation). Some rwlock implementations give priority to writers to prevent writer starvation.
Q16. What is a barrier in concurrent programming? Medium
A barrier is a synchronization point where all participating threads must arrive before any can proceed.
pthread_barrier_t barrier;
pthread_barrier_init(&barrier, NULL, num_threads);
void *thread_work(void *arg) {
// Phase 1: each thread does independent work
compute_partial_result(arg);
// All threads must finish Phase 1 before any can start Phase 2
pthread_barrier_wait(&barrier); // Block until all threads reach here
// Phase 2: work that depends on all Phase 1 results being done
use_all_results();
return NULL;
}
Use cases:
- Parallel algorithms with multiple phases (parallel merge sort: all partial sorts must finish before merge).
- Simulation steps: all agents must complete time step T before starting T+1.
- Scientific computing: all MPI ranks synchronize between computation phases.
Java equivalent: CyclicBarrier or CountDownLatch.
Q17. What is lock-free programming? Hard
Lock-free programming uses atomic hardware instructions (CAS, fetch-and-add, load-linked/store-conditional) instead of locks to achieve thread safety without blocking.
Guarantees:
| Level | Guarantee |
|---|---|
| Wait-free | Every thread completes its operation in a finite number of steps, regardless of other threads |
| Lock-free | At least one thread always makes progress; individual threads may be delayed indefinitely |
| Obstruction-free | A thread makes progress if it runs in isolation |
Lock-free stack (push operation):
struct Node { int val; Node *next; };
atomic<Node*> top;
void push(int val) {
Node *new_node = new Node{val, nullptr};
Node *old_top;
do {
old_top = top.load();
new_node->next = old_top;
} while (!top.compare_exchange_strong(old_top, new_node));
// CAS: if top still == old_top, set top = new_node; else retry
}
Why lock-free?
- No deadlock (no locks to deadlock on).
- No priority inversion.
- Better scalability on many-core systems.
- Immune to thread failures holding locks.
Challenges: ABA problem, memory reclamation (when can freed nodes be safely deallocated?), correctness proofs are hard.
Classic Problems
Q18. Solve the Producer-Consumer (Bounded Buffer) problem using semaphores. Medium
Problem: Producer generates items, consumer consumes them, bounded buffer of size N. Producer must not overflow; consumer must not underflow.
semaphore mutex = 1; // Binary semaphore for buffer access
semaphore empty = N; // Count of empty slots (starts at N)
semaphore full = 0; // Count of filled slots (starts at 0)
// Producer
void producer() {
while (true) {
item = produce_item();
wait(empty); // Wait for an empty slot
wait(mutex); // Enter critical section
add_item(buffer, item);
signal(mutex); // Exit critical section
signal(full); // Signal that one more slot is full
}
}
// Consumer
void consumer() {
while (true) {
wait(full); // Wait for a full slot
wait(mutex); // Enter critical section
item = remove_item(buffer);
signal(mutex); // Exit critical section
signal(empty); // Signal that one more slot is empty
consume_item(item);
}
}
Key ordering: wait(empty/full) BEFORE wait(mutex). If reversed, deadlock: consumer holds mutex, waits for full which producer can never signal because producer cannot get mutex.
Q19. Solve the Readers-Writers problem. Medium
Problem: Multiple readers can read simultaneously. A writer needs exclusive access.
First readers-writers (readers priority):
semaphore mutex = 1; // Protect readcount
semaphore rw_mutex = 1; // Exclusive access for writers
int readcount = 0;
// Reader
void reader() {
wait(mutex);
readcount++;
if (readcount == 1) wait(rw_mutex); // First reader locks out writers
signal(mutex);
// READ DATA (multiple readers can be here simultaneously)
wait(mutex);
readcount--;
if (readcount == 0) signal(rw_mutex); // Last reader lets writers in
signal(mutex);
}
// Writer
void writer() {
wait(rw_mutex); // Exclusive access
// WRITE DATA
signal(rw_mutex);
}
Problem: Writers can starve if readers arrive continuously (readcount never reaches 0).
Second readers-writers (writers priority): When a writer is waiting, no new readers are admitted. More complex; prevents writer starvation.
In practice: pthread_rwlock_t in POSIX. Many OS implementations give preference to writers once waiting to prevent writer starvation.
Q20. Solve the Dining Philosophers problem. Hard
Problem: 5 philosophers sit at a table with a fork between each pair. To eat, a philosopher needs both adjacent forks. How to prevent deadlock and starvation?
Naive solution (causes deadlock):
All philosophers pick up left fork simultaneously.
All wait for right fork (held by neighbor).
Circular wait -> deadlock.
Solution 1: Asymmetric rule
Philosophers 0-3: pick up left fork first, then right.
Philosopher 4: pick up right fork first, then left.
Breaks circular wait.
Solution 2: Allow only N-1 philosophers to try simultaneously
semaphore limit = 4; // At most 4 can try to eat at once
void philosopher(int i) {
wait(limit);
wait(fork[i]);
wait(fork[(i+1) % 5]);
eat();
signal(fork[i]);
signal(fork[(i+1) % 5]);
signal(limit);
think();
}
Solution 3: Monitor-based (Tanenbaum's)
enum State { THINKING, HUNGRY, EATING }
State[] state = new State[5]; // All start THINKING
Semaphore[] self = new Semaphore[5]; // One per philosopher
synchronized void take_forks(int i) {
state[i] = HUNGRY;
test(i); // Try to eat
if (state[i] != EATING) self[i].wait(); // If cannot eat, wait
}
synchronized void put_forks(int i) {
state[i] = THINKING;
test(LEFT(i)); // Let left neighbor try to eat
test(RIGHT(i)); // Let right neighbor try to eat
}
void test(int i) {
if (state[i] == HUNGRY && state[LEFT(i)] != EATING && state[RIGHT(i)] != EATING) {
state[i] = EATING;
self[i].signal();
}
}
Q21. What is the Sleeping Barber problem? Medium
Problem: A barber has one barber chair and N waiting chairs. When no customers, barber sleeps. Customer arrives: if barber asleep, wakes him. If waiting chairs are full, customer leaves. How to synchronize?
semaphore customers = 0; // Number of waiting customers
semaphore barber = 0; // Barber ready (0 = asleep, 1 = ready)
semaphore mutex = 1; // Protect waiting count
int waiting = 0;
int CHAIRS = N;
// Barber process
void barber_process() {
while (true) {
wait(customers); // Sleep if no customers
wait(mutex);
waiting--;
signal(barber); // Barber is ready to cut
signal(mutex);
cut_hair();
}
}
// Customer process
void customer_process() {
wait(mutex);
if (waiting < CHAIRS) {
waiting++;
signal(customers); // Wake barber
signal(mutex);
wait(barber); // Wait for barber to be ready
get_haircut();
} else {
signal(mutex); // No room, leave
}
}
Analogies in CS:
- Thread pool with bounded work queue.
- Network server with accept queue.
- Connection pool management.
Q22. What is priority inversion? How is it solved? Hard
Priority inversion: A high-priority task waits for a low-priority task to release a resource, while medium-priority tasks preempt the low-priority task, preventing it from running.
Priority: H > M > L
1. L acquires lock on resource R
2. H tries to acquire R, blocks on L
3. M arrives (M > L), preempts L
4. M runs to completion
5. L finally gets CPU, releases R
6. H gets R, runs
Problem: H effectively had priority < M, even though H > M.
Actual execution order: L (partial) -> M -> L (rest) -> H
Expected: L should be paused for H, not M.
Solution 1: Priority Inheritance Protocol (PIP) When L holds a resource needed by H, L temporarily inherits H's priority. This prevents M from preempting L.
After H blocks on R (held by L):
L's effective priority = max(H's priority, L's original priority)
L now runs at H's priority -> M cannot preempt L
L finishes, releases R, priority reverts to original
H runs
Solution 2: Priority Ceiling Protocol (PCP) Each resource has a ceiling = highest priority of any task that may acquire it. A task can only acquire a resource if its priority > current ceiling. Prevents priority inversion at the cost of sometimes blocking unnecessarily.
Historical: NASA Mars Pathfinder (1997) suffered system resets due to priority inversion in VxWorks. Engineers enabled priority inheritance remotely to fix it.
Q23. How would you implement a thread-safe bounded queue (BlockingQueue)? Hard
import java.util.concurrent.locks.*;
public class BoundedBlockingQueue<T> {
private final T[] buffer;
private int head = 0, tail = 0, count = 0;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
@SuppressWarnings("unchecked")
public BoundedBlockingQueue(int capacity) {
buffer = (T[]) new Object[capacity];
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == buffer.length) {
notFull.await(); // Wait if buffer full
}
buffer[tail] = item;
tail = (tail + 1) % buffer.length;
count++;
notEmpty.signal(); // Wake a waiting consumer
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // Wait if buffer empty
}
T item = buffer[head];
head = (head + 1) % buffer.length;
count--;
notFull.signal(); // Wake a waiting producer
return item;
} finally {
lock.unlock();
}
}
}
Design choices:
- Single
ReentrantLockprevents races on head/tail/count. - Two condition variables (
notEmpty,notFull) allow targeted signaling. whilenotiffor condition checks (handles spurious wakeups).try/finallyensures lock is always released.- Circular buffer O(1) enqueue and dequeue.
Advanced Concurrency
Q24. What is the Java Memory Model (JMM)? What is the volatile keyword? Hard
The Java Memory Model defines the rules for how threads interact through memory (when writes by one thread are visible to another).
Without JMM guarantees, CPUs can:
- Reorder instructions for performance.
- Cache variables in registers (thread sees stale value from its cache, not RAM).
volatile keyword:
- Guarantees that reads and writes to a volatile variable go directly to main memory (no caching).
- Guarantees ordering: operations before a volatile write happen-before the volatile write; volatile read happens-before operations after it.
volatile boolean ready = false;
int data = 0;
// Thread T1 (writer)
data = 42;
ready = true; // volatile write
// Thread T2 (reader)
while (!ready); // volatile read
System.out.println(data); // Guaranteed to print 42 (happens-before through volatile)
volatile does NOT make compound operations atomic:
volatile int counter = 0;
counter++; // NOT atomic (read-modify-write). Use AtomicInteger for this.
synchronized vs volatile:
synchronized: mutual exclusion AND memory visibility.volatile: memory visibility only, no exclusion. Lighter weight.
Q25. What is the difference between optimistic and pessimistic locking? Hard
| Aspect | Pessimistic Locking | Optimistic Locking |
|---|---|---|
| Assumption | Conflicts are likely; lock upfront | Conflicts are rare; proceed without lock, check at commit |
| Mechanism | Acquire mutex/lock before access | Read data, record version; at write time, check version unchanged |
| Overhead | Lock acquisition (even if no conflict) | Version check; retry on conflict |
| Best for | High-contention scenarios | Low-contention scenarios |
| Deadlock risk | Yes | No (no locks held during operation) |
| Example | synchronized block in Java, SELECT ... FOR UPDATE in SQL | Java AtomicInteger.compareAndSet(), Git merge, database MVCC |
Optimistic locking in Java:
AtomicInteger value = new AtomicInteger(0);
// Optimistic update: retry until CAS succeeds
int oldVal, newVal;
do {
oldVal = value.get();
newVal = oldVal + 1;
} while (!value.compareAndSet(oldVal, newVal));
// No lock needed; retry if value changed between get() and compareAndSet()
Q26. What is a thread pool? Why is it used instead of creating threads on demand? Medium
A thread pool is a fixed set of pre-created threads that wait for tasks. Instead of creating a new thread for each task, tasks are submitted to a queue; idle pool threads pick them up.
Why thread pools:
| Problem with on-demand threads | Thread pool solution |
|---|---|
| Thread creation is slow (~1ms) | Threads pre-created, no creation overhead per task |
| Unlimited thread growth can exhaust RAM | Fixed pool size bounds memory usage |
| Too many threads = context switch overhead dominates | Pool size tuned to CPU count |
| Hard to control parallelism | Pool size controls concurrency |
Java ExecutorService:
// Create a pool with 4 threads
ExecutorService pool = Executors.newFixedThreadPool(4);
// Submit tasks
for (int i = 0; i < 100; i++) {
int taskId = i;
pool.submit(() -> processTask(taskId));
}
// Shutdown (wait for all tasks to complete)
pool.shutdown();
pool.awaitTermination(60, TimeUnit.SECONDS);
Pool sizing rules of thumb:
- CPU-bound tasks: pool size = number of CPU cores.
- I/O-bound tasks: pool size = cores * (1 + wait_time / compute_time). More threads compensate for I/O blocking.
Q27. What is false sharing and how do you fix it? Hard
False sharing occurs when two threads modify different variables that happen to reside on the same CPU cache line, causing the cache line to be invalidated and reloaded repeatedly.
Cache line: CPUs load memory in 64-byte (typical) cache lines. If Thread T1 on CPU1 modifies variable A and Thread T2 on CPU2 modifies variable B, and A and B are on the same cache line:
CPU1 writes A -> invalidates cache line on CPU2
CPU2 reads B -> cache miss -> must reload from RAM
CPU2 writes B -> invalidates cache line on CPU1
CPU1 reads A -> cache miss
...
Both threads are repeatedly invalidating each other's cache despite accessing DIFFERENT variables.
Detection: Performance profilers show very high cache miss rates for seemingly unrelated variables.
Fix: Padding
struct BadCounter {
int counter1; // same 64-byte cache line as counter2
int counter2;
};
struct GoodCounter {
int counter1;
char padding[60]; // Pushes counter2 to next cache line
int counter2;
};
Java: @Contended annotation (JEP 142):
@sun.misc.Contended
public class Counter {
public volatile long value;
}
// JVM adds padding automatically around value
Real-world impact: On high-core-count systems, false sharing can reduce throughput by 10-100x for naive multi-threaded counter or flag implementations.
Q28. How does garbage collection interact with multi-threading? Hard
Garbage collection (GC) in managed languages (Java, Python, Go) interacts with threads in several ways:
Stop-the-world pauses: Most GC algorithms require all application threads to pause while the GC runs (to ensure a consistent view of the heap for object graph traversal).
All threads paused -> GC runs (marks live objects, sweeps dead) -> All threads resume
Pause duration: proportional to heap size and live object count.
Concurrent GC (Java G1, ZGC): Modern collectors do most work concurrently with application threads, minimizing stop-the-world pauses. ZGC targets sub-millisecond pauses even on multi-terabyte heaps.
Write barriers: Concurrent GC requires write barriers: every reference write in the application is intercepted by a small piece of code that notifies the GC. Ensures GC's object graph view remains consistent even as the application modifies it.
Thread-local allocation buffers (TLAB): Each thread has a private region of the heap (TLAB) for fast object allocation without locking. Objects are allocated into the TLAB without any synchronization. When full, a new TLAB is requested from the GC (requires synchronization only then).
Lock convoy and GC: When GC pauses all threads, threads waiting for locks are also paused. When GC resumes, all threads restart simultaneously, causing a thundering herd effect on contested locks (lock convoy). High-throughput applications tune GC pause time to reduce this.
FAQ
Q: Can a deadlock occur with a single thread using a non-reentrant mutex? Yes. A non-reentrant mutex, when locked twice by the same thread, results in the thread waiting for itself. This is sometimes called self-deadlock.
Q: What is the difference between wait() and sleep() in Java?
sleep() pauses the thread for a specified time while retaining any locks held. wait() must be called inside a synchronized block; it releases the lock and waits until notified. wait() is for inter-thread coordination; sleep() is simply for pausing execution.
Q: How many threads should a typical Java backend service use?
For CPU-bound tasks: number of available processors (Runtime.getRuntime().availableProcessors()). For I/O-bound tasks (REST services): typically 50-200 threads per service instance, tuned by load testing. Virtual threads (Java 21 Project Loom) change this dramatically, allowing millions of lightweight threads.
Related PapersAdda guides:
Methodology applied to this articlelast verified 8 Jun 2026
- No fabricated salary numbers or success rates. If we quote a range, it's sourced.
- No noun-substituted templates. This article was not generated by swapping company names in a stock prompt.
- No paid placements, sponsored coaching links, or affiliate-shilled course pushes.
Explore this topic cluster
More resources in Interview Questions
Use the category hub to browse similar questions, exam patterns, salary guides, and preparation resources related to this topic.
Paid contributor programme
Sat this this year? Share your story, earn ₹500.
First-person experience reports help future candidates prep smarter. We pay verified contributors ₹500 via UPI per accepted story - with byline.
Submit your story →Ready to practice?
Take a free timed mock test
Put what you learned into practice. Our mock tests match the 2026 pattern with timer, navigator, reveal, and score breakdown. No signup.
Start Free Mock Test →More from PapersAdda
Top 15 Product Companies Hiring Freshers India 2026: Compensation + Bar + Interview Loop
Accenture Interview Process 2026: Rounds & Prep
Accenture Interview Questions 2026 (with Answers for Freshers)
Adobe Interview Process 2026: Rounds, OA & Aptitude