issue 117apr 27mmxxvi
est. 2017
Sun, 27 Apr 2026
vol. IX · no. 117
PapersAdda
placement intelligence, since 2017
640+ briefs · 24 campuses · by reservation
verified offers · sourced from r/developersIndia
razorpay₹65.00 LPA· iit-d · sde-1google₹54.00 LPA· iiit-h · swe-imicrosoft₹49.50 LPA· iit-b · sdeatlassian₹38.00 LPA· nit-w · sde-1amazon₹44.20 LPA· bits-p · sde-1uber₹42.00 LPA· iit-kgp · sde-1razorpay₹65.00 LPA· iit-d · sde-1google₹54.00 LPA· iiit-h · swe-imicrosoft₹49.50 LPA· iit-b · sdeatlassian₹38.00 LPA· nit-w · sde-1amazon₹44.20 LPA· bits-p · sde-1uber₹42.00 LPA· iit-kgp · sde-1

Top 28 OS Threads and Concurrency Interview Questions (2026)

27 min read
Interview Questions
Updated: 8 Jun 2026
Aditya Sharma
Aditya's Edit

PapersAdda 2026 Placement Cycle

By Aditya Sharma·Founder & Editor, PapersAdda

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

  1. Thread Fundamentals (Q1-Q8)
  2. Synchronization Primitives (Q9-Q17)
  3. Classic Problems (Q18-Q23)
  4. 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

AspectConcurrencyParallelism
DefinitionStructuring a system to deal with multiple tasks that may overlap in timeMultiple tasks literally execute at the same instant
Hardware requirementWorks on a single CPU (time-multiplexed)Requires multiple CPUs or cores
GoalCorrectness and responsivenessSpeed (throughput)
ExampleA single-core CPU running a web server that handles multiple requests via context switchingA 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

AspectUser-Level Thread (ULT)Kernel-Level Thread (KLT)
Managed byUser-space library (e.g., GNU Pth, Java green threads)OS kernel
Context switchFast (library code, no syscall)Slower (kernel mode switch)
I/O blockingOne block = ALL threads in process blockOnly the blocked thread blocks
ParallelismNone on multi-core (kernel sees 1 thread)True parallelism (kernel schedules on multiple cores)
ExampleOld Java virtual machine threads, Go goroutines (M:N model)POSIX pthreads on Linux, Java threads since JDK 1.3

Thread models:

ModelDescription
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):

RequirementDescription
Mutual ExclusionAt most one process is in its critical section at any time
ProgressIf 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 WaitingAfter 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

AspectProcessThread
Scheduling unitYes (the process context)Yes (kernel threads are independently scheduled)
Context switch costHigh (full address space, TLB flush)Lower (shared address space, only register/stack swap)
CommunicationIPC (pipes, sockets, shared memory, signals)Shared memory directly (same address space)
Fault isolationStrong (crash in P1 does not affect P2)Weak (crash in one thread kills all threads in process)
Creation timeSlow (fork + copy-on-write setup)Fast (new stack + register set only)
Typical switch time1-10 microseconds0.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:

MutexSemaphore
OwnershipYes (only locker can unlock)No (any thread can signal)
Value range0 or 10 to N
Primary useMutual exclusionBoth 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() and signal() (or notify()).
// 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:

SemaphoreMonitor
Abstraction levelLow-levelHigh-level
Error proneYes (easy to misuse wait/signal ordering)No (compiler enforces structure)
Condition variablesNo built-inYes
Language supportOS/library callJava 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

AspectSpinlockMutex
Waiting methodBusy-wait (spin in loop)Block (sleep, context switch)
CPU usage while waitingHigh (100% for that core)Zero (thread sleeps)
Context switchNone while spinningRequired to sleep and wake
Good forVery short critical sections, multi-core, no sleeping insideLonger critical sections, any blocking inside
Kernel useInterrupt handlers, very short kernel critical sectionsUser-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:

LevelGuarantee
Wait-freeEvery thread completes its operation in a finite number of steps, regardless of other threads
Lock-freeAt least one thread always makes progress; individual threads may be delayed indefinitely
Obstruction-freeA 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:

  1. Single ReentrantLock prevents races on head/tail/count.
  2. Two condition variables (notEmpty, notFull) allow targeted signaling.
  3. while not if for condition checks (handles spurious wakeups).
  4. try/finally ensures lock is always released.
  5. 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

AspectPessimistic LockingOptimistic Locking
AssumptionConflicts are likely; lock upfrontConflicts are rare; proceed without lock, check at commit
MechanismAcquire mutex/lock before accessRead data, record version; at write time, check version unchanged
OverheadLock acquisition (even if no conflict)Version check; retry on conflict
Best forHigh-contention scenariosLow-contention scenarios
Deadlock riskYesNo (no locks held during operation)
Examplesynchronized block in Java, SELECT ... FOR UPDATE in SQLJava 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 threadsThread pool solution
Thread creation is slow (~1ms)Threads pre-created, no creation overhead per task
Unlimited thread growth can exhaust RAMFixed pool size bounds memory usage
Too many threads = context switch overhead dominatesPool size tuned to CPU count
Hard to control parallelismPool 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
Sources used
Public exam-pattern documents, official recruiter pages, and verified candidate reports on r/developersIndia and LinkedIn.
Verification window
Page last edited 8 Jun 2026 by Aditya Sharma. Numbers and patterns sanity-checked against the most recent 2026 cycle drives we tracked.
What we did NOT do
  • 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.
Verification policy: /editorial-standards/. Found something incorrect? Submit a correction - we respond within 48 hours.

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

Share this guide: