110 Concurrency Interview Questions and Answers (2026)

Concurrency isn't a nice-to-have anymore. As systems scale to handle more users and more data, teams lean on concurrent code to stay fast, and interviewers now expect you to actually understand it, not just recognize the buzzwords. Walk in shaky on race conditions, deadlocks, or memory visibility, and it shows fast.
This guide gives you 110 questions with concise, interview-ready answers, code included where it clarifies the point. It's structured Junior to Mid to Senior, so you start with fundamentals and build toward the deep stuff like lock-free atomics and memory models. Work through it and you'll answer with real fluency, not guesswork.
Q1.Can you explain the conceptual difference between concurrency and parallelism? Can a single-core machine achieve both?
Concurrency is about structure (dealing with many tasks by interleaving their progress), while parallelism is about execution (physically running multiple tasks at the same instant). A single core can do concurrency but not true parallelism.
Concurrency: composition of independent tasks:
Tasks make progress by interleaving; the CPU switches between them so they appear simultaneous.
It is a way of structuring a program, independent of the hardware.
Parallelism: literally at the same time: Requires multiple execution units (cores) running instructions in the same instant.
Single-core answer:
Yes to concurrency: time-slicing interleaves tasks so many are in flight.
No to parallelism: only one instruction stream executes at any moment.
Rob Pike's framing: concurrency is dealing with many things at once; parallelism is doing many things at once.
Q2.Explain the difference between a thread and a process in terms of memory isolation and communication overhead. Why are threads often preferred for high-performance concurrency?
A process owns its own isolated memory space, while threads live inside a process and share its memory. That shared memory makes thread communication cheap and creation lightweight, which is why threads are often favored for high-performance concurrency, at the cost of needing synchronization.
Process: isolated memory:
Each has its own address space; one crashing doesn't corrupt another.
Communication needs IPC (pipes, sockets, shared memory segments), which is slower and involves the kernel or copying.
Thread: shared memory within a process:
Threads share the heap and globals; they have their own stack and registers.
Communication is just reading/writing shared variables: near-zero copying cost.
Why threads are preferred for performance:
Cheaper to create and context-switch (no address-space swap).
Fast, direct data sharing avoids IPC overhead.
The tradeoff: Shared mutable state introduces race conditions, requiring locks/atomics, and one thread can crash the whole process.
Q3.Why does concurrency often lead to non-deterministic behavior in a program?
Concurrency is non-deterministic because the exact interleaving of independent tasks is decided by the scheduler at runtime, and that ordering varies between runs. When tasks share mutable state, different interleavings produce different results.
The scheduler controls timing: You don't control exactly when a task is paused or resumed; the OS/runtime decides, influenced by load, cores, and timers.
Interleavings multiply: Operations from different tasks can be woven together in many valid orders, and each run may pick a different one.
Shared mutable state exposes it: A non-atomic operation like count += 1 (read, add, write) can interleave with another task's version, losing updates: a race condition.
Consequences: Bugs are hard to reproduce (Heisenbugs); correctness requires synchronization or avoiding shared state, not luck in timing.
Q4.What is the difference between CPU-bound and I/O-bound tasks, and how does that change your choice of concurrency model?
A CPU-bound task is limited by processor speed (heavy computation), while an I/O-bound task spends most of its time waiting on external resources (disk, network, DB). This distinction dictates whether you need true parallelism or just efficient waiting.
CPU-bound:
Bottleneck is the CPU; the task is busy computing, not waiting.
Needs parallelism: use multiple processes/cores (in Python, multiprocessing to sidestep the GIL). More threads on one core won't help.
I/O-bound:
Bottleneck is waiting for external I/O; the CPU is mostly idle.
Needs concurrency to overlap the waits: threads or async (asyncio) let one task wait while another runs.
Choosing the model:
Many concurrent I/O waits: async/event loop scales to thousands of tasks cheaply.
Heavy computation: multiple processes to actually use more cores.
Mixed: offload CPU work to a process pool while keeping I/O async.
Q5.Can you walk through the typical lifecycle and states of a thread, from creation to termination?
A thread moves through a fixed set of lifecycle states from the moment it's created until it terminates, driven by scheduling decisions and blocking events. In Java these map to the Thread.State enum, but the concept is universal.
New / Created: The thread object exists but hasn't started; no OS thread yet. It enters the system once you call start().
Runnable / Ready: Eligible to run and waiting for the scheduler to give it a core. "Running" (actually executing on a CPU) is usually a substate of runnable.
Blocked / Waiting:
Blocked: waiting to acquire a lock/monitor.
Waiting / Timed-waiting: paused on wait(), join(), sleep(), or I/O until notified or a timeout elapses.
Terminated / Dead: Its run method returned or threw. A terminated thread cannot be restarted.
Key point for interviews: transitions between runnable and blocked/waiting are frequent and driven by locks, I/O, and the scheduler, not by your code directly.
Q6.Explain the Check-Then-Act race condition.
Check-then-act is a race condition where you observe some state (the check) and then act on it, but the state can change between the two steps because they aren't atomic. The classic examples are lazy initialization and "if absent, then add."
The window of vulnerability: Between the check and the act, another thread can change the state, so your action is based on a stale observation.
Common forms:
Lazy init: if (instance == null) instance = new X(); can create two instances.
Put-if-absent, or checking a balance before withdrawing.
Fixes: Make check and act one atomic unit: hold a lock around both, or use an atomic primitive like compareAndSet / putIfAbsent.
Q7.Why is shared mutable state the fundamental source of difficulty in concurrent programming, and what design strategies minimize it?
State that is both shared and mutable is the root problem because multiple threads can interleave their reads and writes in nondeterministic orders, producing race conditions, torn/invisible updates, and broken invariants. Remove either "shared" or "mutable" and the difficulty largely disappears.
Why it's the core issue:
Interleavings are combinatorial and nondeterministic, so bugs are intermittent and hard to reproduce.
Without synchronization there's no guarantee one thread sees another's writes (visibility) or that compound operations stay atomic.
Strategies to minimize it:
Don't share: confine state to one thread (thread confinement, stack/local variables, ThreadLocal).
Don't mutate: use immutable objects so sharing is automatically safe.
When you must share mutable state, guard it: encapsulate it behind a lock, or use concurrent/atomic data structures.
Communicate instead of sharing: message passing / actors / queues transfer ownership rather than sharing memory.
Q8.What is the read-modify-write problem, and why do operations like incrementing a counter require synchronization?
Read-modify-write means an operation reads a value, computes a new value from it, and writes it back as three separate steps; if two threads interleave those steps, updates can be lost. Incrementing a counter is the classic example because count++ is not atomic.
Why it breaks: Two threads both read the same old value, both add one, and both write the same result: two increments produce a net gain of one (a lost update).
Why synchronization is required:
The read, compute, and write must appear as one indivisible (atomic) step so no other thread can observe or overwrite the in-between state.
Options: a lock around the compound action, an atomic type (AtomicInteger.incrementAndGet()), or a CAS loop.
Common misconception: Making the field volatile fixes visibility but NOT atomicity, so volatile count++ is still racy.
Q9.What is the fundamental difference between a mutex and a semaphore? In what scenario would you use a counting semaphore instead of a binary mutex?
A mutex enforces mutual exclusion with ownership (the thread that locks must unlock), while a semaphore is a signaling counter that permits up to N holders and has no notion of ownership.
Mutex (mutual exclusion lock):
Protects a critical section: exactly one thread inside at a time.
Has an owner: only the locking thread may unlock it.
Semaphore (counting):
Holds a permit count; acquire() decrements, release() increments.
No ownership: any thread can release, so it works as a signal between threads.
A binary semaphore (count 1) resembles a lock but lacks ownership/reentrancy guarantees.
When to use a counting semaphore:
Limiting access to a pool of N identical resources (e.g. 10 DB connections, a rate limiter).
Producer/consumer signaling where you count available items or slots.
Rule of thumb: Protecting one shared state: mutex. Controlling how many can proceed at once, or cross-thread signaling: semaphore.
Q10.What is 'callback hell,' and how do futures/promises or async/await address it?
async/await address it?"Callback hell" is deeply nested, hard-to-read code that arises when asynchronous steps are chained by passing callbacks into callbacks. Futures/promises flatten this into a linear chain, and async/await makes async code read like ordinary sequential code.
The problem with nested callbacks:
Each async result triggers another callback, growing rightward into a "pyramid of doom."
Error handling is duplicated and fragile: every callback must check and forward errors manually.
Control flow (loops, conditionals, early return) becomes awkward.
How promises/futures help:
They represent an eventual value, so steps chain via .then() instead of nesting.
Errors propagate down the chain to a single handler (.catch()).
How async/await helps:
await suspends until the awaited value resolves, so code is written top-to-bottom.
Ordinary try/catch, loops, and conditionals work as usual.
Q11.What is the 'Producer-Consumer' problem, and what synchronization primitives are typically used to solve it?
The producer-consumer problem is a classic synchronization scenario where one or more producers generate items into a shared, bounded buffer while one or more consumers remove them: the challenge is coordinating access so producers wait when the buffer is full, consumers wait when it's empty, and no data races corrupt the buffer.
The core constraints:
Mutual exclusion: only one thread mutates the shared buffer at a time.
Producers block on a full buffer; consumers block on an empty one (bounded-buffer variant).
Typical primitives:
A mutex / lock to protect the buffer.
Condition variables (notEmpty and notFull) so waiting threads sleep and are signaled on state change.
Or two counting semaphores (emptySlots, fullSlots) plus a mutex: semaphores track capacity, mutex guards the buffer.
Higher-level abstraction: A thread-safe blocking queue packages all of the above so application code just calls put() / take().
Common pitfall: use a while-loop (not if) when re-checking the wait condition to guard against spurious wakeups and lost signals.
Q12.What is a blocking queue, and why is it a natural fit for the producer-consumer pattern?
A blocking queue is a thread-safe FIFO that automatically blocks a producer's insert when the queue is full and a consumer's remove when it's empty: it encapsulates the locking and condition-variable signaling that the producer-consumer problem requires, so application code just puts and takes.
What it provides:
put() blocks while the queue is at capacity; take() blocks while it's empty.
All enqueue/dequeue operations are internally synchronized (no external lock needed).
Why it fits producer-consumer:
It is exactly the shared bounded buffer plus the guarded-suspension waiting rules, packaged as one object.
A bounded queue supplies natural backpressure: fast producers slow down when consumers fall behind, bounding memory use.
Decouples producers and consumers so they can run at different rates and scale independently.
Examples: Java's BlockingQueue (e.g. ArrayBlockingQueue), Python's queue.Queue, Go's buffered channel.
Q13.What does it mean for a workload to be 'embarrassingly parallel,' and why do such problems scale so well?
A workload is embarrassingly parallel when it splits into independent subtasks that need little or no communication or coordination, so they can run in parallel with almost no synchronization overhead.
Defining trait: independence:
Each subtask works on its own slice of data and produces its own result, with no shared mutable state between them.
No locks, no inter-task messaging, no ordering constraints during the compute phase.
Why they scale well:
With no communication, the serial fraction is tiny, so Amdahl's Law permits near-linear speedup.
Adding cores adds throughput almost directly, limited mainly by scheduling and final result aggregation.
Typical examples: Monte Carlo simulations, rendering separate image tiles, brute-force key search, applying a filter to independent records, map-style batch jobs.
Caveat: Perfect scaling still bends at the edges: input distribution, disk/network I/O, and the final gather/reduce step can reintroduce a serial bottleneck.
Q14.What is the difference between Cooperative and Preemptive multitasking?
In cooperative multitasking a task keeps the CPU until it voluntarily yields, while in preemptive multitasking a scheduler can forcibly interrupt a task at any time to give another a turn. The difference is who controls when a switch happens.
Cooperative multitasking:
A task runs until it explicitly yields (e.g. at an await point); switches happen only at known points.
Pro: low overhead, predictable, fewer race conditions between yield points.
Con: one misbehaving task that never yields (or blocks) starves everything.
Preemptive multitasking:
The scheduler interrupts tasks on a timer or priority, regardless of their cooperation.
Pro: one task can't monopolize the CPU; fairness is enforced.
Con: switches can occur mid-operation, so shared state needs synchronization.
Examples: OS threads are preemptive; asyncio coroutines are cooperative.
Q15.What is a context switch, and why is it computationally expensive? How do lightweight concurrency models like coroutines mitigate this cost?
A context switch is the act of saving one execution context's state and loading another's so the CPU can switch what it runs. It's expensive because of the direct save/restore work plus indirect costs like polluted caches, and lightweight coroutines cut this by switching in user space without kernel involvement.
What gets switched:
CPU registers, program counter, and stack pointer are saved and the next task's are restored.
For processes, the memory mappings (page tables) change too, flushing the TLB.
Why it's costly:
Direct cost: entering the kernel and moving register/state data.
Indirect cost: CPU caches and TLB are now cold for the new task, causing misses; this often dominates.
How coroutines mitigate it:
They switch in user space (no kernel trap), just swapping a small saved state at yield points.
They keep running on the same thread, so caches stay warmer and no TLB flush occurs.
Being cooperative, switches happen only at await points, avoiding needless preemptions.
Q16.What are the primary overhead costs associated with multi-threading?
Multi-threading isn't free: you pay in memory for each thread, in CPU time for context switching and synchronization, and in developer complexity from managing shared state. These costs are why unbounded threading backfires.
Memory overhead: Each thread reserves its own stack (often ~1 MB), so thousands of threads consume significant RAM.
Context-switching overhead: Saving/restoring state plus cold caches and TLB after a switch; grows as runnable threads exceed cores.
Synchronization overhead: Locks, mutexes, and atomics cost time and can serialize execution; contention makes threads wait on each other.
Creation/teardown cost: Spinning up threads takes time, which is why thread pools reuse them.
Complexity cost: Race conditions, deadlocks, and non-deterministic bugs make correct threaded code hard to write and debug.
Q17.What is thread oversubscription, and how does having more runnable threads than cores affect performance?
Oversubscription is having more runnable threads than available CPU cores, so the OS must time-slice them. Modest oversubscription hides I/O latency, but heavy oversubscription of CPU-bound work degrades throughput because the machine spends time switching rather than computing.
Context-switch overhead: Saving/restoring register state and updating the scheduler costs cycles that do no useful work.
Cache and TLB pollution: Each switched-in thread evicts the previous thread's warm cache lines, so more time is lost to cache misses than to the switch itself.
It's not always bad:
For I/O-bound work, extra threads are usually blocked, so overlapping them raises utilization.
For CPU-bound work, the ideal pool size is near the core count (or core count + 1).
Rule of thumb: Size CPU-bound pools to cores; size I/O-bound pools higher based on the fraction of time spent waiting.
Q18.What is thread confinement, how do ThreadLocal variables help achieve it, and what are the risks of using them in a thread-pooled environment?
ThreadLocal variables help achieve it, and what are the risks of using them in a thread-pooled environment?Thread confinement means restricting data so only one thread ever touches it, eliminating the need for synchronization. ThreadLocal implements this by giving each thread its own private copy of a variable, but in thread pools those copies persist across tasks and can leak state or memory.
Thread confinement: If an object is reachable from only one thread, no other thread can race on it, so it's inherently safe without locks (e.g. stack/local variables).
How ThreadLocal helps: Each thread gets an independent value via get()/set(), so shared-looking state is actually per-thread. Common for non-thread-safe helpers like SimpleDateFormat or a request context.
Risks in a pooled environment:
State leakage: pool threads are reused, so a value set by one task is still visible to the next task on that thread. Always clean up with remove(), ideally in a finally block.
Memory leaks: long-lived threads keep the ThreadLocal value alive; combined with classloader references this can pin objects (a classic app-server leak).
Doesn't propagate across threads: if the task hands work to another thread, the confined value won't follow.
Q19.What does it mean for a class to be 'Thread-Safe'? Does using thread-safe collections make your entire application thread-safe?
A class is thread-safe if it behaves correctly when accessed from multiple threads concurrently, regardless of scheduling, and with no required external synchronization from the caller. Using thread-safe collections does NOT make your whole application thread-safe: safety doesn't compose automatically.
What thread-safe guarantees: Each individual operation on the object maintains its invariants under concurrent access, with visibility and atomicity handled internally.
Why safe collections aren't enough: Individual calls are atomic, but sequences of calls are not. A ConcurrentHashMap makes get and put safe, but check-then-act across two calls still races.
Composition breaks it: Application invariants span multiple objects/operations, which no single collection can protect. You need atomic compound methods (putIfAbsent, compute) or your own locking around the compound action.
Q20.Why is immutability such a powerful tool for concurrency, and are there performance trade-offs to using copy-on-write strategies?
Immutable objects can't change after construction, so they're inherently thread-safe: with no writes there are no data races, no locks, and no visibility problems to reason about. Copy-on-write trades that safety for extra allocation on every mutation.
Why immutability is powerful:
No mutable state means no interleaving to protect: an immutable object is safe to share freely across threads.
It sidesteps visibility issues: once safely published, all threads see a fully-constructed, consistent object.
It composes: immutable pieces can be combined without new synchronization.
Copy-on-write (COW) trade-offs:
Reads are lock-free and fast, ideal for read-heavy, write-rare data.
Every write copies the whole structure: O(n) per mutation plus GC pressure, so it's poor for write-heavy workloads.
Persistent/structural-sharing data structures reduce copy cost by reusing unchanged subtrees.
Practical note: Examples: Java's String and CopyOnWriteArrayList; use COW where reads vastly outnumber writes.
Q21.How would you implement thread-safe lazy initialization, and what are the pitfalls?
Thread-safe lazy initialization means creating an object only on first use while guaranteeing all threads see one fully-constructed instance. The simplest correct forms are synchronizing the accessor, or (for statics) using the lazy holder idiom; the classic mistake is unsynchronized or broken double-checked locking.
Simple synchronized accessor: Correct and easy, but every access pays lock cost even after initialization.
Holder class idiom (for statics): Relies on the JVM's guarantee that a class is initialized lazily and safely on first reference: no explicit locking, no volatile.
Double-checked locking (for instance fields): The field MUST be volatile; otherwise another thread can see a non-null but partially-constructed object due to reordering.
Pitfalls:
Missing volatile in double-checked locking is a subtle, real bug (unsafe publication).
Racy lazy init can create the object multiple times, which is only acceptable if the object is immutable/stateless and duplicates are harmless.
Q22.What is the difference between synchronized/wrapper collections and truly concurrent collection implementations?
Wrapper collections just add a single lock around an ordinary collection, while truly concurrent collections are redesigned internally for concurrent access with far less contention.
Synchronized wrappers (e.g. Collections.synchronizedMap()):
Wrap a plain collection so every method acquires one lock on the whole structure.
Serializes all access: only one thread operates at a time, so they don't scale.
Compound actions (check-then-act, iteration) still need external locking to be safe.
Truly concurrent collections (e.g. ConcurrentHashMap, CopyOnWriteArrayList):
Use internal techniques (lock striping, CAS, copy-on-write) so multiple threads proceed in parallel.
Provide atomic compound operations (putIfAbsent, compute) so callers avoid external locks.
Iterators are weakly consistent: they don't throw ConcurrentModificationException and tolerate concurrent updates.
Bottom line: Wrappers give correctness through a global lock; concurrent collections give correctness plus scalability.
Q23.What is the difference between Optimistic and Pessimistic Locking? When would you prefer one over the other?
Pessimistic locking assumes conflicts are likely and locks data up front; optimistic locking assumes conflicts are rare and instead detects them at commit time using a version check, retrying on failure.
Pessimistic locking:
Acquire a lock (e.g. SELECT ... FOR UPDATE) before touching data; others block.
Guarantees no lost updates but reduces concurrency and risks deadlocks.
Optimistic locking:
Read data with a version/timestamp; on update, verify the version is unchanged.
If it changed, the write fails and the operation retries: no locks held during the work.
When to prefer each:
Optimistic: low contention, mostly reads, short transactions (better throughput).
Pessimistic: high contention or costly retries, long transactions where conflicts are common.
Q24.Explain the Condition Variable and why you must always call wait() inside a loop.
wait() inside a loop.A condition variable lets a thread atomically release a lock and sleep until another thread signals that some state changed; you must re-check that state in a loop because a wakeup does not guarantee the condition is actually true.
What it does:
Always used with a lock: wait() releases the lock and blocks; signal()/notify() wakes a waiter, which re-acquires the lock before returning.
Decouples "wait for a condition" from busy-polling.
Why wait() must be in a loop:
Spurious wakeups: a thread can wake without any signal.
Stolen wakeups: another thread may run first and consume the condition before you re-acquire the lock.
Broadcast: notifyAll() wakes many, but only one can proceed; the rest must recheck.
The rule: Re-test the predicate after waking; if still false, wait again.
Q25.What is a 'Reentrant Lock' (or Recursive Lock), and what problem does it solve?
A reentrant lock is one that a thread already holding it can acquire again without blocking itself; it tracks the owner and a hold count, releasing only when unlocks match locks. It solves the self-deadlock problem when a lock-holding method calls another method that needs the same lock.
How it works:
Records the owning thread and a recursion count.
Same thread re-acquiring increments the count; each unlock decrements it; the lock frees at zero.
Problem it solves:
Without reentrancy, a synchronized method calling another synchronized method on the same object would block waiting on a lock it already owns (deadlock).
Java's synchronized and ReentrantLock are reentrant for this reason.
Caveat: You must unlock exactly as many times as you locked, ideally in finally blocks.
Q26.When would you prefer a ReadWriteLock over a standard reentrant lock, and what is the risk of writer starvation in this model?
ReadWriteLock over a standard reentrant lock, and what is the risk of writer starvation in this model?Prefer a ReadWriteLock when reads greatly outnumber writes: it lets many readers share the lock concurrently while writes are exclusive. The risk is writer starvation, where a steady stream of readers keeps the write lock permanently blocked.
When it helps:
Read-heavy, write-rare shared data (caches, config, lookup tables).
Concurrent readers raise throughput vs a plain mutex that serializes even reads.
When it doesn't: Frequent writes or very short critical sections: the extra bookkeeping can cost more than a simple ReentrantLock.
Writer starvation:
If new readers keep acquiring while a writer waits, the writer may never get exclusive access.
Mitigation: use a fair/write-preferring policy (e.g. ReentrantReadWriteLock with fairness) so a waiting writer blocks incoming readers.
Fairness reduces starvation but lowers peak read throughput: a trade-off.
Q27.Explain the 'Readers-Writers' problem. Why would you use a Read-Write Lock instead of a standard Mutex?
Mutex?The Readers-Writers problem models shared data accessed by many readers (who only observe) and writers (who mutate): readers can safely share access concurrently, but a writer needs exclusive access. A Read-Write Lock encodes exactly this asymmetry, whereas a plain mutex would serialize everything.
The core invariant:
Any number of readers may hold the lock at once (no reader mutates state, so concurrent reads are safe).
A writer must be exclusive: no other reader or writer while it holds the lock.
Why a Read-Write Lock over a standard mutex:
A mutex admits only one holder at a time, so it forces readers to wait on each other even though concurrent reads are harmless.
An RWLock lets read-heavy workloads scale: readers proceed in parallel and only writes serialize.
When it pays off: Read-mostly data (config, caches, routing tables) with rare writes.
Cost / caveat: An RWLock has more internal bookkeeping than a mutex, so for write-heavy or very short critical sections a plain mutex is often faster and simpler.
Q28.What is a Spinlock, and in what specific scenario is it more efficient than a blocking Mutex?
Mutex?A spinlock is a lock where a thread that can't acquire it busy-waits (loops checking the lock) instead of going to sleep. It's more efficient than a blocking mutex when the expected wait is shorter than the cost of a context switch.
How it works: Repeatedly tries an atomic operation (e.g. compare-and-swap) until it wins, keeping the thread on-CPU the whole time.
When spinning wins:
Critical section is very short and contention is low, so the holder releases within a few hundred cycles.
On multi-core hardware where the holder runs on another core and can release while you spin.
Avoids the expensive park/unpark and context-switch overhead a blocking mutex incurs.
When spinning loses:
Long critical sections or high contention: you burn CPU doing nothing.
On a single core, spinning is pathological: the holder can't even run to release the lock.
Hybrid in practice: Many real mutexes are adaptive: they spin briefly, then fall back to blocking.
Q29.How would you coordinate a Producer-Consumer relationship using only a Mutex and a Condition Variable? Why must the 'wait' call always be inside a loop?
Mutex and a Condition Variable? Why must the 'wait' call always be inside a loop?You protect a shared buffer with a mutex and use a condition variable so a consumer waits when the buffer is empty and a producer signals when it adds an item (and vice versa when full). The wait must be inside a loop because a thread can wake without the condition actually being true.
The mechanics:
The mutex guards the buffer; the condition variable lets a thread release the mutex and sleep atomically, then reacquire it on wakeup.
Consumer: lock, while empty wait, take item, unlock, then signal not-full.
Producer: lock, while full wait, add item, unlock, then signal not-empty.
Why wait lives in a while loop, not an if:
Spurious wakeups: a thread may return from wait without any signal.
Stolen wakeups: with notifyAll / multiple waiters, another thread may consume the condition before you reacquire the lock.
So you must re-check the predicate after waking; the loop guarantees you only proceed when the condition genuinely holds.
Q30.What is a barrier (or a CountDownLatch/phaser), and how does it differ from a lock as a synchronization tool?
CountDownLatch/phaser), and how does it differ from a lock as a synchronization tool?A barrier is a synchronization point where a group of threads each wait until all (or a set count) have arrived, then all proceed together. Unlike a lock, which enforces exclusive access to a resource, a barrier coordinates progress across phases of a computation.
Barrier: N threads call await(); the barrier releases them all at once when the Nth arrives. Often reusable across multiple phases (e.g. CyclicBarrier).
CountDownLatch: A one-shot gate: threads await() until a counter reaches zero via countDown(). The counters (who counts down) and the waiters can be different threads; not reusable.
Phaser: Like a barrier but with dynamic party registration and multiple advancing phases.
How it differs from a lock:
A lock grants mutual exclusion (one holder, others blocked from a resource); it says "wait your turn."
A barrier is about rendezvous / ordering: "nobody advances until everyone reaches this point." It coordinates many threads rather than protecting shared state.
Q31.What is a monitor, and how does it combine mutual exclusion with condition-based waiting?
A monitor is a synchronization construct that bundles shared data, a mutex, and one or more condition variables into one abstraction: only one thread executes inside the monitor at a time, and threads can block on conditions and be signaled when state changes. It combines mutual exclusion with condition-based waiting so you don't wire them together by hand.
Mutual exclusion part: Entering a monitor method implicitly acquires its lock, so critical-section code runs one thread at a time.
Condition-waiting part:
Inside the monitor a thread can wait on a condition: it atomically releases the monitor lock and sleeps, letting others enter.
Another thread changes state and issues signal / notify, waking a waiter, which reacquires the lock before continuing.
Signaling semantics: Signal-and-continue (common, e.g. Java): the signaled thread must recheck its predicate in a loop, since the signaler keeps running.
In practice: Java's synchronized methods plus wait()/notify() are a built-in monitor per object.
Q32.What is a try-lock (or timed lock), and how can it be used to avoid deadlock?
A try-lock attempts to acquire a lock and returns immediately (or after a timeout) reporting success or failure instead of blocking indefinitely. This lets a thread back off and retry rather than wait forever, which breaks the circular-wait condition that causes deadlock.
What it does:
try_lock(): grab the lock if free, else return false right away.
Timed variant: wait up to a bound, then give up if not acquired.
How it avoids deadlock:
When acquiring multiple locks, if you get lock A but try_lock on B fails, release A and retry, so you never hold one lock while blocking forever on another.
This removes the "hold-and-wait" / circular-wait deadlock condition.
Caveats:
Naive retry loops can livelock (threads repeatedly grab and release in lockstep); add randomized backoff.
Lock ordering is a simpler deterministic alternative when you know all locks up front; try-lock helps when you can't order them.
Q33.What is a spurious wakeup, and how should code handle it?
A spurious wakeup is when a thread waiting on a condition variable wakes up without any thread having signaled it (or wakes before the condition it's waiting for is actually true). Because they can happen, you must always re-check the predicate in a loop after waking.
Why they happen: Allowed by OS/library semantics (e.g. POSIX pthread_cond_wait) for implementation efficiency, and multiple waiters may be woken while only one can proceed.
The fix: wait in a loop, not an if:
Re-test the predicate on every wake; if it's still false, go back to waiting.
This also correctly handles legitimate wakes where another thread grabbed the resource first.
Rule of thumb: Always pair a condition variable with an explicit boolean predicate guarded by the mutex.
Q34.What are the four Coffman conditions required for a deadlock to occur, and how can you prevent deadlock by breaking one of these conditions?
Deadlock requires all four Coffman conditions to hold simultaneously; break any single one and deadlock becomes impossible.
Mutual exclusion: A resource can be held by only one thread at a time. Break it with lock-free/shareable resources where feasible.
Hold and wait: A thread holds one resource while waiting for another. Break it by acquiring all needed locks atomically up front, or releasing held locks before requesting more.
No preemption: Resources cannot be forcibly taken away. Break it by allowing a thread to back off and release what it holds (e.g. tryLock with timeout).
Circular wait: A cycle of threads each waiting on the next. Break it by imposing a global lock ordering so all threads acquire locks in the same order (the most common practical fix).
Q35.How do you distinguish between Deadlock, Livelock, and Starvation?
All three are liveness failures where threads make no useful progress, but they differ in what the threads are doing: deadlocked threads are stuck waiting, livelocked threads are actively reacting to each other without progressing, and starved threads are runnable but perpetually denied resources.
Deadlock: Threads are blocked forever, each waiting on a resource another holds (circular wait). No thread runs.
Livelock: Threads are not blocked: they keep changing state in response to each other but never progress (two people stepping aside in a hallway repeatedly). CPU is busy, work is not done.
Starvation: One thread makes progress but another is indefinitely denied access (e.g. always outcompeted for a lock, or too low a priority). The system as a whole advances.
Quick discriminator: Deadlock: everyone stuck and idle. Livelock: everyone busy but stuck. Starvation: someone stuck while others proceed.
Q36.How do you prevent Deadlock in a system that requires acquiring multiple locks?
The standard defense is to eliminate circular wait by always acquiring multiple locks in a single, globally consistent order; where that isn't possible, use timed/try-lock acquisition with back-off.
Global lock ordering: Define a total order over locks (e.g. by object ID or address) and always acquire in that order. No cycle can form.
Lock ordering by identity when order is dynamic: For operations like account transfers, compare identifiers and lock the lower one first.
Try-lock with timeout and back-off: Use tryLock; if you can't get the second lock, release the first, wait a randomized interval, and retry. Breaks the no-preemption condition.
Reduce the need entirely: Coarser single lock, lock-free structures, or restructuring so only one lock is held at a time.
Q37.Explain the difference between livelock and starvation, and how can a fair locking strategy help prevent starvation?
In livelock threads stay active but keep responding to one another so none progresses; in starvation a thread is denied a resource indefinitely while others advance. A fair locking strategy prevents starvation by granting the lock in request order so no waiter is skipped forever.
Livelock:
No thread is blocked; they repeatedly retry/yield in reaction to each other (e.g. both back off simultaneously and collide again).
Often caused by naive back-off; fix with randomized/exponential back-off so threads desynchronize.
Starvation: A specific thread never gets the resource: unfair locks may keep handing off to whoever is already running, and low-priority threads lose out.
How fair locking helps:
A fair lock (e.g. new ReentrantLock(true)) maintains a FIFO queue, so the longest-waiting thread acquires next: bounded wait, no indefinite denial.
Trade-off: fairness lowers throughput (more context switches) than the default barging (unfair) lock.
Q38.How do you detect a deadlock in a running application?
You detect deadlock by finding a cycle of threads that are each blocked waiting on a resource held by another in the cycle. In practice you capture a thread dump and look for the tell-tale blocked-waiting cycle, or rely on runtime tooling that does cycle detection for you.
Thread dumps:
In the JVM, jstack or a SIGQUIT dump explicitly reports "Found one Java-level deadlock" with the involved threads and locks.
Native code: gdb / pstack to inspect where each thread is blocked.
Programmatic detection: JVM ThreadMXBean.findDeadlockedThreads() reports cycles at runtime; you can log or alert on it.
Wait-for graph: Conceptual model: nodes are threads, edges are "waiting for lock held by"; a cycle means deadlock. Databases run this continuously and abort a victim transaction.
Symptom-based / preventive: Threads permanently blocked with zero CPU and stalled throughput. Timeouts (tryLock with a deadline) turn silent deadlock into a detectable, recoverable error.
Q39.Explain the Dining Philosophers problem. What does it illustrate about deadlock, and how can it be solved?
The Dining Philosophers problem models five philosophers around a table who each need two shared forks (left and right) to eat; if every philosopher grabs their left fork simultaneously, each waits forever for a right fork that never comes. It is the classic illustration of deadlock arising from shared resource acquisition.
What it illustrates:
All four Coffman deadlock conditions at once: mutual exclusion (a fork is held by one), hold-and-wait (holding left, waiting for right), no preemption, and circular wait (each waits on a neighbor).
Also demonstrates starvation and fairness concerns, not just deadlock.
Common solutions:
Resource ordering: number the forks and always pick up the lower-numbered one first, breaking the circular wait.
Limit concurrency: allow at most N-1 philosophers to try eating at once (a semaphore), guaranteeing at least one can proceed.
Arbiter/waiter: a central mediator grants permission to pick up forks, serializing acquisition.
Try-and-back-off: attempt to grab both forks, release the first if the second is unavailable (risks livelock without randomized backoff).
Q40.What is the volatile keyword's effect on visibility and instruction reordering?
volatile keyword's effect on visibility and instruction reordering?volatile makes a variable's reads and writes go directly to main memory (never a stale cached copy) and prevents the compiler and CPU from reordering operations across the volatile access. It provides visibility and ordering, but not atomicity for compound operations.
Visibility: A write to a volatile is immediately visible to any thread that later reads it; readers never see a stale cached value.
Ordering (in the Java model):
A volatile write acts as a release and a volatile read as an acquire: writes before a volatile write are visible to a thread after it reads that volatile.
Prevents the compiler/CPU from moving ordinary memory operations across the volatile access.
What it does NOT do:
No atomicity for read-modify-write: count++ on a volatile is still racy; use atomics or locks.
Note: in C/C++, volatile is about memory-mapped hardware, not thread synchronization; use std::atomic there instead.
Q41.What is the difference between marking a variable as volatile versus using an Atomic type? Does volatile guarantee atomicity for increment operations?
volatile versus using an Atomic type? Does volatile guarantee atomicity for increment operations?volatile only guarantees visibility (reads/writes go to main memory and aren't cached or reordered away); an atomic type guarantees both visibility and atomicity of a read-modify-write. So no: volatile does not make count++ atomic.
volatile: visibility and ordering only: Each read sees the latest write; establishes a happens-before edge, but each operation is independent.
Why increment breaks: count++ is three steps (read, add, write); two threads can read the same value and one increment is lost.
Atomic types: indivisible RMW: Operations like getAndIncrement() or compareAndSet() execute as one uninterruptible unit, usually via a CAS instruction.
Rule of thumb: Use volatile for a simple flag written by one thread and read by others; use an atomic (or a lock) for counters and any compound update.
Q42.Concurrency correctness is often framed around three concerns: atomicity, visibility, and ordering. Can you explain what each one means?
These three are the independent axes of thread-safety: atomicity is about operations not being interrupted mid-way, visibility is about one thread's writes being seen by another, and ordering is about the sequence in which operations appear to occur. A correct concurrent program must handle all three.
Atomicity:
A compound action (read-modify-write) completes as one indivisible unit so no other thread sees an intermediate state.
Violation example: two threads doing count++ and losing an update.
Visibility:
A write by one thread is guaranteed to be seen by another rather than hidden in a cache/register.
Violation example: a reader loops forever on a stale boolean flag.
Ordering:
Operations appear to execute in an expected order; compilers/CPUs may reorder otherwise.
Violation example: seeing a published reference before the fields it points to are written.
How they're addressed: Locks give all three; volatile gives visibility and ordering but not atomicity; atomics give atomicity plus visibility for a single variable.
Q43.What is Backpressure, and why is it critical in concurrent data pipelines?
Backpressure is a feedback mechanism where a slow consumer signals a fast producer to slow down, preventing the producer from overwhelming the system with more data than can be processed.
The core problem it solves: If a producer emits faster than a consumer handles, unbounded buffers grow until memory exhausts (OOM) or latency explodes.
How it works:
The consumer communicates demand (pull-based) or the channel blocks/rejects when full, throttling the producer.
Bounded queues are the simplest form: a full queue forces the producer to wait.
Common strategies when overwhelmed: Buffer (bounded), block/throttle the producer, drop items (sampling), or error out.
Why it's critical in pipelines:
Keeps memory and latency bounded, prevents cascading failures, and maintains stability under load spikes.
It's a first-class concept in reactive systems (Reactive Streams, RxJava) and async channels (Go buffered channels, asyncio.Queue).
Q44.Explain how a single-threaded event loop can handle thousands of concurrent I/O requests without threads.
A single-threaded event loop handles thousands of connections because I/O waiting is not CPU work: instead of blocking a thread per request, it registers interest in many sockets and processes each only when the OS says it's ready.
The key insight: Most requests spend time waiting on network or disk, not computing; a blocked thread is wasted capacity.
OS-level readiness notification: The loop uses epoll, kqueue, or IOCP to monitor many file descriptors at once and wake only when one is ready.
The loop cycle: Ask the OS for ready events, run the associated callback/coroutine to the next await point, then move on: connections are interleaved on one thread.
Why it scales: Each idle connection costs only a small bit of memory, not a full thread stack (~1MB) plus context-switch overhead.
The catch: Any CPU-heavy or blocking call stalls all connections, since there's only one thread doing the work.
Q45.What is the conceptual difference between a Future and a Promise? How do they help manage the 'callback hell' problem?
Future and a Promise? How do they help manage the 'callback hell' problem?A Future is a read-only placeholder for a value that will arrive later; a Promise is the writable side that produces (resolves or rejects) that value. They tame callback hell by turning nested callbacks into flat, composable objects you can chain.
Future (the consumer side): Represents a result not yet available; you read from it or attach continuations.
Promise (the producer side):
The handle used to fulfill the future exactly once with a value or error.
Terminology varies: in JavaScript a Promise merges both roles; Java's CompletableFuture is both.
How they fix callback hell:
Callbacks nest deeply ("pyramid of doom"); futures are first-class values you compose linearly with .then() / map.
Errors propagate down the chain to one handler instead of being handled at every level.
They enable combinators like all/race, and are what async/await is built on syntactically.
Q46.When would you prefer an Asynchronous (Event-Loop) model over a Multithreaded model?
Prefer an event-loop model when the workload is I/O-bound with high concurrency: lots of connections that mostly wait. Prefer threads when work is CPU-bound or you depend on blocking libraries.
Choose async/event-loop when:
Many concurrent I/O operations: web servers, proxies, chat, API gateways.
You want low memory per connection and to avoid thread-pool exhaustion.
Shared state is simpler: cooperative scheduling means fewer data races (yields only at await points).
Choose multithreading when:
CPU-bound work that must run in parallel on multiple cores (though threads plus a GIL won't help there).
You rely on blocking/synchronous libraries with no async equivalent.
Preemptive scheduling matters: one long task shouldn't be able to starve others.
In practice: Hybrids are common: an event loop plus a threadpool (or process pool) to offload blocking or CPU work.
Q47.Explain the 'Async/Await' model. What happens to the underlying thread when an await point is reached?
await point is reached?Async/await is syntactic sugar over futures that lets you write asynchronous, non-blocking code in a sequential style. At an await point the coroutine suspends and releases the thread back to the event loop, which uses it to run other work instead of sitting idle.
async marks a coroutine: Calling it returns a future/coroutine object rather than running to completion immediately.
await yields control: It pauses the current coroutine until the awaited operation resolves, without blocking the thread.
What happens to the thread:
The thread is not parked or blocked: it returns to the event loop and executes other ready tasks.
One thread thus multiplexes many coroutines, which is why async scales for I/O.
Key caveat: A blocking or CPU-heavy call between awaits holds the thread and stalls every other coroutine on that loop.
Q48.What is the 'GIL' (Global Interpreter Lock) in languages like Python or Ruby, and how does it impact multi-core utilization?
GIL' (Global Interpreter Lock) in languages like Python or Ruby, and how does it impact multi-core utilization?The GIL is a mutex in interpreters like CPython and MRI Ruby that allows only one thread to execute bytecode at a time. It simplifies memory management but means threads cannot run Python/Ruby code in true parallel across multiple cores.
What it is: A single global lock the interpreter must hold to run bytecode, protecting internal state like reference counts from races.
Impact on multi-core:
CPU-bound multithreaded code doesn't speed up: threads take turns, serialized by the GIL.
I/O-bound code still benefits: the GIL is released during blocking I/O, so threads overlap their waits.
Workarounds:
Use multiprocessing for CPU parallelism (each process has its own interpreter and GIL).
Offload to C extensions/NumPy that release the GIL, or use async for I/O concurrency.
Note: The GIL is an implementation detail of CPython, not the Python language; Jython and free-threaded CPython (PEP 703) avoid it.
Q49.What is the difference between blocking and non-blocking I/O, and how does non-blocking I/O let a small number of threads serve many connections?
With blocking I/O a thread issues a call and is suspended until the data is ready, so it can do nothing else meanwhile. With non-blocking I/O the call returns immediately (with data or "not ready"), letting one thread multiplex many connections via a readiness mechanism instead of dedicating a thread to each.
Blocking I/O:
One connection ties up one thread for its entire wait.
10k idle connections need ~10k threads: heavy memory and context-switch cost (the C10k problem).
Non-blocking I/O:
Calls return instantly; sockets are set to non-blocking mode.
A readiness selector (epoll/kqueue) lets one thread watch thousands of sockets and act only on the ready ones.
Why few threads serve many connections:
Most connections are idle at any instant; a thread only spends time on sockets with actual work.
Time is spent on active work, not parked in kernel waits, so a handful of event-loop threads scale to huge connection counts.
Caveat: non-blocking helps I/O waits, not CPU work: a slow handler still stalls the loop and must be offloaded.
Q50.How can futures/promises be composed and chained, and how does error propagation work through such a chain?
Futures/promises compose by chaining transformations, where each stage receives the previous stage's resolved value and returns a new future. Errors short-circuit the chain: a failure skips the success stages and flows to the nearest error handler, much like exceptions.
Chaining (sequential):
.then() / thenApply / await feed one result into the next step.
Returning a future from a stage flattens it (no nested future-of-future).
Combining (parallel): Fan-out/fan-in with Promise.all / asyncio.gather waits for all; Promise.race takes the first.
Error propagation:
A rejected/failed future skips downstream success handlers until a .catch() / try/catch handles it.
A handler that recovers returns a resolved value, so the chain continues; re-throwing keeps it propagating.
Trap: an unhandled rejection can be silently swallowed if no terminal handler exists.
Q51.What are virtual (or green) threads, and how do they differ from traditional OS/platform threads? Why are they particularly useful for I/O-bound tasks?
Virtual (green) threads are lightweight threads scheduled by a runtime rather than the OS, so millions can exist cheaply. They shine for I/O-bound work because when one blocks on I/O, the runtime parks it and runs another on the same OS thread, giving blocking-style code with async-style scalability.
Platform (OS) threads:
Managed and scheduled by the kernel; each has a large fixed stack (often ~1MB).
Creation and context switches are relatively expensive, limiting practical counts to thousands.
Virtual/green threads:
Managed by a runtime scheduler (e.g. JVM Project Loom, Go goroutines) and multiplexed onto a few carrier OS threads.
Small, growable stacks; creating millions is feasible.
Why great for I/O-bound tasks:
On a blocking I/O call the runtime unmounts the virtual thread and mounts another, so the OS thread stays busy.
You write simple sequential blocking code but get event-loop-level concurrency, no explicit async/await coloring.
Caveat: they don't add CPU parallelism beyond carrier threads, and truly blocking native calls can still pin a carrier.
Q52.Why do we use thread pools instead of creating a new thread for every task? What are the risks of a thread pool that is too small versus one that is too large?
Thread pools reuse a bounded set of worker threads to run many tasks, avoiding the cost of creating/destroying a thread per task and capping concurrency so the system isn't overwhelmed. Sizing matters: too small starves throughput and can deadlock, too large wastes resources and thrashes.
Why pool instead of thread-per-task:
Thread creation is expensive (memory, kernel setup); reuse amortizes it.
A bound limits total threads, preventing resource exhaustion under load spikes.
A queue smooths bursts by buffering pending tasks.
Pool too small:
Underutilized CPU/hardware and long queue latency: tasks wait for a free worker.
Deadlock risk if pooled tasks block waiting on other tasks that can't get a thread.
Pool too large:
Excessive context switching and cache contention reduce throughput.
High memory use and possible overload of downstream resources (DB connections, etc.).
Sizing rule of thumb:
CPU-bound: near the number of cores.
I/O-bound: more than cores, scaled by the wait-to-compute ratio.
Q53.How does a thread pool work, and what factors should you consider when deciding the optimal number of threads for a pool for CPU-bound vs I/O-bound work?
A thread pool keeps a fixed set of pre-created worker threads that pull tasks from a shared queue, so you reuse threads instead of paying thread-creation cost per task and cap the number of threads competing for the CPU. Optimal size depends on whether tasks burn CPU or wait on I/O.
How it works:
Tasks are submitted to a queue; idle workers dequeue and run them, then loop back for the next task.
Bounds concurrency and reuses threads, avoiding unbounded thread creation and context-switch thrashing.
CPU-bound sizing:
Threads are busy almost all the time, so more threads than cores just add context-switching overhead.
Rule of thumb: pool size ≈ number of cores (or cores + 1 to cover the occasional stall).
I/O-bound sizing:
Threads spend most of their time blocked waiting, so you can run many more than cores to keep the CPU utilized.
Approximation: threads ≈ cores × (1 + wait time / compute time). High wait/compute ratios justify large pools.
Other factors:
Queue type and bound: an unbounded queue hides overload; a bounded queue plus a rejection policy applies backpressure.
Downstream limits (DB connection pool, external API rate limits) often cap the useful thread count more than CPU does.
Separate pools for CPU vs I/O work so a slow I/O batch can't starve compute tasks.
Q54.What are coroutines and continuations, and how do they enable lightweight cooperative concurrency?
A coroutine is a function that can suspend itself and later resume where it left off, rather than running to completion in one go. A continuation is the captured state of "the rest of the computation" at a suspension point: it's what lets the coroutine resume exactly where it paused. Together they enable cooperative concurrency: tasks yield control voluntarily instead of being preempted by the OS.
Coroutine: Can pause at an await/yield point, hand control back to a scheduler, and resume later with its local state intact.
Continuation:
A first-class representation of the suspended state (local variables, position in the code) that the runtime stores and invokes to resume.
Suspension is often implemented by transforming the function into a state machine driven by continuations.
Why this is lightweight and cooperative:
No OS thread per task and no kernel context switch: suspend/resume is a cheap in-process operation, so millions can coexist.
Cooperative means a coroutine only yields at explicit points, so between suspensions it has the CPU uninterrupted (simpler reasoning, but a non-yielding coroutine can hog the scheduler).
Q55.Compare the 'Shared Memory' model with the 'Message Passing' (CSP) model. What are the trade-offs?
In the shared memory model threads communicate by reading and writing common memory, coordinating with locks and other synchronization. In the message passing (CSP) model, tasks have no shared state and communicate by sending values over channels. The slogan is "don't communicate by sharing memory; share memory by communicating."
Shared memory:
Pros: fast (no copying), and a natural fit for the hardware, which is already shared memory.
Cons: needs explicit synchronization (locks, atomics); prone to data races, deadlocks, and subtle memory-ordering bugs that are hard to test.
Message passing / CSP:
Pros: no shared mutable state, so no data races by construction; ownership of data is clear as it moves through channels; easier to reason about and to distribute across machines.
Cons: copying/serialization overhead; can still deadlock (e.g. everyone blocked on a channel); an extra indirection to design around.
Trade-off summary:
Shared memory optimizes for raw local performance at the cost of correctness effort; message passing optimizes for safety and scalability at the cost of some copying.
They aren't mutually exclusive: Go channels are built on shared memory underneath, and CSP is often the safer default even on one machine.
Q56.Describe the producer-consumer problem. Why is a bounded buffer usually preferred over an unbounded one in production systems?
The producer-consumer problem is a coordination pattern where one or more producers generate items into a shared buffer and one or more consumers remove and process them, requiring synchronization so producers don't overwrite and consumers don't read empty slots. A bounded buffer is usually preferred because its fixed capacity gives you natural backpressure and protects against memory exhaustion.
The problem:
Decouples producers from consumers so they can run at different, varying rates.
Needs synchronization: mutual exclusion on the buffer, block consumers when empty, and (if bounded) block producers when full: classically done with a mutex plus two condition variables or counting semaphores.
Why bounded is preferred in production:
Backpressure: when the buffer fills, producers block (or the submission is rejected), signaling upstream to slow down instead of piling up work.
Bounded memory: an unbounded queue under a fast producer / slow consumer grows without limit until the process runs out of memory.
Predictability: capacity bounds latency (an item waits behind at most N others) and surfaces overload early rather than as a sudden crash.
When unbounded is acceptable: Only when producer rate is provably bounded below consumer rate, or bursts are small and short-lived.
Q57.In Go-style concurrency (CSP), why is it said to 'Share memory by communicating, not communicate by sharing memory'?
CSP), why is it said to 'Share memory by communicating, not communicate by sharing memory'?CSP-style concurrency (as in Go) favors passing data over channels between independent goroutines rather than having multiple threads mutate the same shared variables behind locks: ownership of the data moves with the message, so at any moment one goroutine 'owns' it and no lock is needed.
"Communicate by sharing memory" (the thing to avoid):
Multiple threads read/write the same memory, coordinated by locks; correctness depends on every access respecting the locking discipline.
Easy to get subtle races, deadlocks, and forgotten locks.
"Share memory by communicating" (the preferred model):
Data is handed off through a channel; the sender relinquishes access and the receiver becomes the sole owner.
Synchronization is implicit in the send/receive, so there's no separate lock to forget.
Why it helps:
Single ownership at a time eliminates the class of data races that shared mutation invites.
Composition is clearer: pipelines and fan-out/fan-in are expressed as channels connecting stages.
Caveat: it's a guideline, not a law. Go still provides sync.Mutex for simple shared state (e.g. a counter or cache), which is sometimes clearer than a channel.
Q58.What is the difference between task parallelism and data parallelism? Give an example of a workload suited for each.
Data parallelism applies the same operation to many pieces of data in parallel; task parallelism runs different operations (tasks) concurrently, possibly on the same or different data. In short: data parallelism splits the data, task parallelism splits the work.
Data parallelism:
One kernel/function, N data partitions, run simultaneously (SIMD, GPU shaders, parallelFor).
Example: multiply every element of a huge array by a scalar, or apply a filter to each pixel of an image.
Task parallelism:
Distinct, often heterogeneous tasks execute at once; they may communicate or have dependencies.
Example: a web request that concurrently queries a database, calls a payment API, and renders a template.
Practical notes:
Data parallelism usually scales more predictably (uniform work, easy to partition).
Real systems often mix both (task-parallel stages, each internally data-parallel).
Q59.What is pipeline (dataflow) parallelism, and when is it a good decomposition strategy?
Pipeline (dataflow) parallelism splits a computation into a sequence of stages connected by queues, where each stage processes an item and passes it to the next: like an assembly line, all stages run concurrently on different items, so throughput is bounded by the slowest stage rather than the total latency of one item.
How it works:
Stage 1 processes item N+2 while stage 2 processes N+1 and stage 3 processes N.
Stages communicate through bounded/blocking queues, which also provide backpressure.
When it's a good fit:
A continuous stream of items passing through the same ordered sequence of transformations (parsing then decoding then encoding).
Stages have distinct resource profiles (one I/O-bound, one CPU-bound), so overlapping them uses hardware better.
Trade-offs:
Throughput is limited by the slowest stage; balance stages or replicate the bottleneck stage.
Adds latency and complexity per item; poor for one-off computations with no stream of work.
Q60.How does the map-reduce pattern express parallelism, and what kinds of problems fit it?
Map-reduce expresses parallelism in two phases: a map phase applies a function independently to each input record (embarrassingly parallel), producing key-value pairs, and a reduce phase aggregates all values sharing a key. Because map tasks don't depend on each other, they scale across many workers, and the framework handles partitioning, shuffling, and fault tolerance.
The two phases:
Map: (k1, v1) -> list(k2, v2), run in parallel over input splits.
Shuffle: group intermediate values by key across the cluster.
Reduce: (k2, list(v2)) -> result, parallel across distinct keys.
Problems that fit:
Aggregations over large datasets: word counts, log analysis, building inverted indexes, computing sums/averages by group.
Work that is naturally per-record independent in the map step, with associative/commutative aggregation in reduce.
Poor fits:
Iterative algorithms with tight interdependencies (many graph/ML algorithms) where the shuffle overhead per iteration dominates.
Low-latency or streaming needs (classic batch map-reduce is throughput-oriented).
Q61.How do you decompose a problem for parallel execution, and how does task granularity affect the outcome?
You decompose a problem by finding independent units of work (by splitting data, by splitting tasks, or by pipeline stages), identifying the dependencies between them, and choosing a task granularity that balances parallelism against coordination overhead. Granularity is the key tuning knob: too fine and overhead dominates, too coarse and cores sit idle.
Decomposition strategies:
Data decomposition: partition the data, run the same work on each partition.
Task decomposition: identify distinct operations that can run concurrently.
Pipeline decomposition: split into ordered stages that overlap on streaming items.
Analyze dependencies: Independent tasks parallelize freely; ordering and shared-state dependencies force synchronization or serialization.
Granularity trade-off:
Fine-grained: many small tasks give good load balancing but the scheduling, synchronization, and communication overhead can exceed the useful work.
Coarse-grained: fewer large tasks minimize overhead but risk load imbalance and idle cores if tasks are uneven.
Aim for tasks large enough to amortize overhead yet numerous enough to keep all cores busy.
Reality check with Amdahl's Law: The serial fraction caps speedup; decomposition should target the largest parallelizable portion, not just add more tasks.
Q62.What is Amdahl's Law, and how does it help you determine the maximum theoretical speedup of a program when adding more CPU cores?
Amdahl's Law states that the speedup of a program from parallelization is capped by its serial fraction: the part that cannot be parallelized. Even with infinite cores, that fixed serial portion sets a hard ceiling on how fast the whole program can get.
The formula:
Speedup = 1 / ( (1 - P) + P/N ), where P is the parallelizable fraction and N is the number of cores.
As N approaches infinity, speedup approaches 1 / (1 - P), the serial ceiling.
What it tells you:
If 95% is parallel, max speedup is 20x no matter how many cores you add.
Small serial fractions dominate at scale: shaving the serial part matters more than adding cores.
Practical use: It sets expectations and guides where to optimize: diminishing returns from more hardware once you near the ceiling.
Key assumption: It fixes the problem size. When problem size grows with cores, Gustafson's Law gives a rosier picture.
Q63.How does introducing concurrency typically affect a system's throughput versus its latency? When might adding more threads actually decrease performance?
Concurrency generally raises throughput (more work completed per unit time) by keeping the CPU busy during waits, but it can hurt the latency of an individual request due to queuing and contention. Beyond a certain point, adding threads reduces performance because overhead outgrows the gains.
Throughput usually improves: While one task waits on I/O, another runs, so total work per second rises.
Latency can worsen per task: An individual task may wait its turn (queuing) and share CPU, so its wall-clock time can increase even as aggregate throughput grows.
When more threads hurt:
Context-switch overhead dominates once runnable threads far exceed cores.
Lock contention serializes work, so threads spend time waiting on each other.
Cache thrashing and memory pressure from many stacks reduce efficiency.
For CPU-bound work, threads beyond core count give no gain, only overhead.
Rule of thumb: there is an optimal concurrency level; past it you see diminishing then negative returns.
Q64.What is the technical distinction between a race condition and a data race, and can you have one without the other?
A data race is a specific low-level phenomenon: two threads access the same memory location concurrently, at least one writes, and there's no synchronization ordering them. A race condition is a broader correctness bug where the outcome depends on the timing/interleaving of operations. They overlap but are not the same, and you can have either without the other.
Data race (memory-level): Defined by the memory model: unsynchronized conflicting accesses; formally undefined behavior in C/C++, and visibility/reordering hazards in Java.
Race condition (logic-level): Correctness depends on the order of events, e.g. a check-then-act or a transfer split into non-atomic steps.
Race condition without a data race: Two threads each use properly synchronized (atomic) operations, but the composite logic still interleaves wrongly: e.g. two AtomicInteger reads then a write. Each access is safe, the sequence is not.
Data race without a (harmful) race condition: Unsynchronized access that happens to be benign in intent (e.g. a racy lazy-init or a stats counter) is still technically a data race and undefined/unsafe even if you "don't care" about the exact value.
Takeaway: Fixing data races (with locks/atomics) does not automatically fix race conditions; you must also make compound operations atomic.
Q65.What does it mean for a function to be Reentrant? Is every thread-safe function reentrant?
A function is reentrant if it can be safely interrupted mid-execution and called again ("re-entered") before the first call completes, and still work correctly. This is a stricter, largely independent property from thread safety: not every thread-safe function is reentrant, and not every reentrant function is thread-safe.
Requirements for reentrancy:
No reliance on mutable shared/static state; use only local variables or caller-supplied storage.
Must not call non-reentrant functions.
Reentrant vs thread-safe:
Thread-safe = correct under concurrent calls, often achieved with locks.
Reentrant = correct under re-entry (including from signal handlers or recursion), typically achieved by avoiding shared state entirely.
Why thread-safe ≠reentrant: A function made thread-safe by taking a non-recursive lock is NOT reentrant: if the same thread re-enters it (e.g. from a signal handler), it deadlocks on its own lock. Reentrancy usually means locking-free.
Q66.Explain the Copy-On-Write strategy and its benefits/drawbacks in a concurrent environment.
Copy-On-Write (COW) makes mutations create a fresh copy of the underlying data rather than modifying it in place, so readers always see a stable, immutable snapshot without locking. It's ideal for read-heavy, write-rare workloads but expensive when writes are frequent.
How it works: Each write copies the whole structure, applies the change, then atomically swaps in the new version. Existing readers keep iterating the old snapshot safely.
Benefits:
Lock-free reads: no synchronization on the hot read path, so high read throughput.
No ConcurrentModificationException: iterators traverse an immutable snapshot.
Drawbacks:
Costly writes: copying the entire structure per mutation is O(n) in time and memory.
Stale reads: iterators may reflect data as it was at snapshot time, not current state.
When to use: Read-mostly collections like listener/subscriber lists (CopyOnWriteArrayList), not high-churn data.
Q67.What are the risks of using ThreadLocal variables in a thread-pooled environment?
ThreadLocal variables in a thread-pooled environment?Thread pools reuse threads across many unrelated tasks, so a ThreadLocal value set during one task silently survives into the next task on that same thread unless you explicitly clear it.
State leakage between tasks: A value left in a thread-local (user context, transaction, tenant id) is visible to the next unrelated task that runs on that pooled thread, causing wrong or cross-request data.
Memory leaks: Pooled threads live for the app's lifetime, so objects held by a ThreadLocal are never garbage collected and accumulate; in Java this is worsened by classloader retention.
Broken assumptions with async/continuation code: If work migrates to a different thread (async, reactive, work-stealing), the thread-local isn't there anymore, so it stops being a reliable propagation mechanism.
Mitigation: Always clear in a finally block (call remove(), not just reset to null), or prefer explicit context passing / scoped values over thread-locals in pooled environments.
Q68.How do you categorize classes by thread-safety, immutable, thread-safe, conditionally thread-safe, and thread-hostile?
These categories describe how much a caller must do to use a class safely from multiple threads, ranging from "nothing at all" (immutable) to "cannot be used concurrently even with external locking" (thread-hostile).
Immutable: State never changes after construction, so it's always thread-safe with no synchronization (e.g. String).
Thread-safe: Mutable but manages its own synchronization internally; every operation is correct under concurrent access with no external locking (e.g. ConcurrentHashMap).
Conditionally thread-safe: Individual operations are safe, but some sequences (compound actions like iterate-then-modify) need client-side locking (e.g. iterating a Collections.synchronizedList while synchronized on it).
Thread-hostile: Cannot be made safe for concurrent use even with external locking, usually because it mutates shared global state (e.g. a class that modifies process-wide statics).
Not covered but common: thread-compatible: Not safe alone, but can be used concurrently if the caller wraps all access in its own synchronization.
Q69.What is a benign race condition, and how does it differ from a harmful one?
A benign race condition is one where the interleaving is technically nondeterministic but every possible outcome is still correct, so it does no harm; a harmful race can leave the program with wrong results or a broken invariant.
Benign race:
All outcomes are acceptable, so the nondeterminism doesn't matter (e.g. a hit counter used only for rough stats, or racy lazy caching where any thread's result is equally valid).
Still requires care: you must ensure values are safely published/visible, so "benign" doesn't mean "ignore memory model".
Harmful race: Some interleaving produces an incorrect result or corrupt state (lost updates, check-then-act on a value another thread changed, broken invariants).
Key distinction: It's not about whether a race exists, but whether any of its outcomes violate correctness; benign races are the rare, deliberately-analyzed exception, not the default assumption.
Q70.How would you approach designing a thread-safe class: what strategies exist for encapsulating and protecting mutable state?
Designing a thread-safe class starts with identifying all mutable state and its invariants, then choosing a policy that keeps every state transition atomic and consistent, usually by encapsulating the state behind synchronization the class fully controls.
Steps to reason it through:
Identify the state: which fields make up the object's state.
Identify invariants: relationships between fields that must always hold, and which operations are compound.
Establish a synchronization policy: which lock guards which state and how compound actions stay atomic.
Strategies for protecting state:
Instance confinement: keep mutable state private and guard every access with the object's own lock so no reference escapes.
Immutability: make state final and unchangeable so no protection is needed.
Delegation: hand thread safety off to already-thread-safe components (e.g. store state in a ConcurrentHashMap or AtomicReference).
Guarding related fields together: variables tied by an invariant must be read/written under the same lock.
Pitfalls to avoid:
Letting mutable state escape (returning a reference to an internal collection) defeats confinement.
Delegation only works if invariants don't span multiple independent thread-safe components.
Q71.What is 'Lock Striping' (or Lock Splitting), and how does it improve the scalability of a concurrent data structure?
Lock striping partitions a data structure into independent segments, each guarded by its own lock, so threads working on different segments proceed in parallel instead of contending for one global lock.
The idea:
Instead of one lock for the whole structure, use an array of N locks ("stripes").
A key maps to a stripe (typically by hash), and only that stripe's lock is taken.
Why it scales:
Reduces contention: up to N threads can write concurrently to different stripes.
Classic example: pre-Java-8 ConcurrentHashMap used segment locks so concurrent puts rarely collided.
Trade-offs:
Operations spanning the whole structure (e.g. resize, size()) must acquire all stripe locks, which is expensive.
More memory overhead and consistent lock-ordering needed to avoid deadlock across stripes.
Q72.What is the difference between coarse-grained and fine-grained locking? How does 'lock striping' help improve the scalability of a concurrent data structure?
Coarse-grained locking uses one lock to protect a large region or the whole structure; fine-grained locking uses many smaller locks so unrelated operations don't contend. Lock striping is a practical form of fine-grained locking that partitions the structure into independently locked segments.
Coarse-grained locking:
Simple and easy to reason about; low overhead when uncontended.
Serializes everything under one lock, so it becomes a bottleneck under load.
Fine-grained locking:
Multiple locks let independent operations run in parallel, improving throughput.
More complex: risk of deadlock (lock ordering) and higher memory/overhead.
Lock striping's role:
Maps keys (usually by hash) to one of N stripe locks, so up to N writers proceed concurrently.
Turns a single hot lock into N cooler ones without needing a lock per element.
Cost: whole-structure operations (resize, size()) must grab all stripes.
Q73.Why can Lock Contention lead to poor CPU utilization even when you have many cores?
Lock contention forces threads to serialize on a shared lock, so even with many cores most threads sit idle (blocked or spinning) while only one makes progress inside the critical section. Adding cores doesn't help because the bottleneck is the lock, not compute.
Serialization defeats parallelism: Only one thread can hold the lock, so the guarded work runs sequentially no matter how many cores exist (this is the serial fraction in Amdahl's Law).
Waiting threads waste resources: Blocking threads trigger context switches and scheduler overhead; spinning threads burn CPU cycles producing no work.
Cache-line effects (contention amplifies): The lock variable bounces between core caches (cache-line ping-pong / false sharing), adding coherence traffic that slows everyone down.
Fixes: Shrink critical sections, shard the lock, use read-write or lock-free structures, or partition data so threads touch independent state.
Q74.Explain the Readers-Writers problem. What are the trade-offs between a 'read-preferring' and a 'write-preferring' implementation?
The Readers-Writers problem asks how to let many readers share data concurrently while giving writers exclusive access. The design choice is who gets priority when both are waiting, and each policy risks starving the other side.
Read-preferring:
New readers may enter even while a writer waits, maximizing read throughput.
Risk: a continuous stream of readers can starve writers indefinitely.
Write-preferring:
Once a writer is waiting, new readers block until the writer finishes, keeping writes timely and data fresh.
Risk: heavy writes can starve readers and reduce read concurrency.
Fair / phased alternative: Serve requests in roughly FIFO order (or alternate batches of readers and writers) to bound starvation for both, at some throughput cost.
How to choose: Read-mostly, staleness-tolerant data favors read-preferring; data where writes must not be delayed favors write-preferring.
Q75.What is priority inversion, and how can a 'priority inheritance' protocol solve it?
Priority inversion occurs when a high-priority task is blocked waiting on a lock held by a low-priority task, while a medium-priority task preempts the low-priority holder, indirectly starving the high-priority task. Priority inheritance fixes it by temporarily boosting the lock holder's priority.
The classic scenario:
Low-priority L holds a lock that high-priority H needs.
Medium-priority M (needing no lock) preempts L, so L never runs to release the lock, so H waits indefinitely.
Priority inheritance protocol:
While L holds a lock that H wants, L inherits H's priority so M cannot preempt it.
When L releases the lock, it reverts to its original priority.
Result: L finishes the critical section quickly and hands off to H.
Real-world note: The Mars Pathfinder rover hit this bug; the alternative is a priority ceiling protocol, which raises the holder to a lock's predefined ceiling.
Q76.How do you identify lock contention in a running application, and what are three strategies to reduce it?
Lock contention shows up as threads spending significant time BLOCKED waiting to acquire a hot lock rather than running; you find it with profilers and thread dumps, then reduce it by shrinking, splitting, or eliminating the lock.
How to identify it:
Sample thread dumps repeatedly: many threads BLOCKED on the same monitor points to a contended lock.
Profilers/lock-profiling (async-profiler lock mode, JFR jdk.JavaMonitorEnter events) report blocked time per lock.
Symptoms: CPU underutilized despite queued work, poor scaling as you add cores.
Three strategies to reduce it:
Narrow the critical section: hold the lock for the shortest possible span, moving work outside it.
Split/shard the lock: lock striping or per-bucket locks (e.g. ConcurrentHashMap) so threads rarely collide on the same lock.
Avoid locking altogether: use lock-free/atomic operations (CAS, AtomicLong), read-write locks for read-heavy data, or thread-local/immutable copies.
Q77.What is a lock convoy, and what conditions cause one to form?
A lock convoy is a performance-killing pattern where multiple threads repeatedly contend for the same lock, and the OS scheduler causes them to line up and hand the lock off in lockstep, so throughput collapses even though no thread is permanently blocked.
What happens:
A thread releases the lock and wakes a waiter, but the released thread often loses its timeslice or the woken thread must be scheduled in, adding context-switch overhead on every acquisition.
Threads spend more time parking, waking, and switching than doing real work.
Conditions that cause one:
A frequently acquired lock held for a very short time by many threads (high contention, short critical section).
Roughly equal-priority threads that keep cycling through the same lock, so a queue of waiters never drains.
Fair/FIFO lock handoff that forces a wakeup and context switch instead of letting a running thread reacquire.
Mitigations:
Reduce lock hold time or lock frequency: shrink or eliminate the critical section.
Use lock-free/atomic structures, per-thread data, or reader-writer splitting.
Prefer locks that allow barging (a running thread reacquires without a full handoff), which breaks the convoy.
Q78.What is the difference between deadlock prevention, avoidance, and detection from a programmer's perspective?
These are three strategies for handling deadlock along a spectrum of cost and restrictiveness: prevention makes deadlock structurally impossible, avoidance uses runtime knowledge to steer around unsafe states, and detection lets deadlock happen then recovers. Most real code uses prevention.
Prevention:
Design so at least one Coffman condition can never hold: e.g., global lock ordering (kills circular wait) or acquiring all locks at once (kills hold-and-wait).
Programmer's view: cheap, static, and the practical default; you enforce a discipline in code.
Avoidance:
At runtime, only grant a resource if the system stays in a safe state (classically the Banker's algorithm).
Programmer's view: needs advance knowledge of max resource needs; rarely practical in general-purpose code.
Detection (and recovery):
Allow deadlock, then find cycles in a wait-for graph and recover by aborting/rolling back a victim.
Programmer's view: common in databases (transaction deadlock detection) where rollback is natural; needs your code to handle retry.
Q79.In the context of a language memory model, what does the 'happens-before' relationship guarantee? Why is it critical for visibility?
Happens-before is a partial ordering the memory model defines between actions: if action A happens-before action B, then A's memory effects are guaranteed visible to B and cannot be reordered past it. Without such a relationship, one thread's writes may never become visible to another, or may appear out of order.
What it guarantees:
Visibility: everything A did (including writes to ordinary variables before A) is observable to B.
Ordering: the compiler/CPU may not reorder in a way that violates the established order.
How it is established:
Program order within a single thread.
Unlock of a monitor happens-before a later lock of the same monitor.
A write to a volatile/atomic happens-before a subsequent read of it.
Thread start/join and transitivity (if A→B and B→C then A→C).
Why it is critical: Modern hardware caches and reorders freely; without a happens-before edge there is no guarantee another thread ever sees your update, so correctness depends entirely on establishing these edges.
Q80.Explain the double-checked locking pattern. Why is the volatile keyword necessary for this pattern to be thread-safe in modern memory models?
volatile keyword necessary for this pattern to be thread-safe in modern memory models?Double-checked locking lazily initializes a shared instance while avoiding locking on the common (already-initialized) path: check the field, lock only if it is null, then check again inside the lock before creating. It requires the field to be volatile because otherwise another thread can observe a non-null but partially constructed object.
The pattern:
First check (unlocked) is a fast path to skip synchronization once initialized.
Second check (inside the lock) prevents two threads that both passed the first check from creating two instances.
Why volatile is required:
Object construction and the write of the reference can be reordered, so a thread could publish the reference before the object's fields are initialized.
volatile forbids that reordering and establishes a happens-before edge so a thread reading a non-null reference sees a fully constructed object.
Without volatile the unlocked read may also see a stale/cached value.
Q81.Why do compilers and CPUs reorder instructions, and how can this break concurrent code that looks correct on paper?
Compilers and CPUs reorder instructions to run faster: as long as a single thread's observable result is unchanged, they are free to rearrange, cache, and overlap operations. In concurrent code this optimization breaks assumptions, because another thread may observe those operations in a different order than the source suggests.
Why reordering happens:
Compilers: hoist loads, sink stores, keep values in registers, and reorder independent statements for pipelining and locality.
CPUs: out-of-order execution, store buffers, and per-core caches let writes become visible to other cores at different times.
The guarantee they preserve: Only single-threaded (as-if-serial) semantics; nothing about cross-thread visibility unless you add synchronization.
How it breaks code:
A classic flag pattern: writing data then setting ready=true may be reordered so another thread sees ready=true before data is written.
Fix: use volatile/atomics, locks, or memory barriers to create happens-before ordering that the compiler and CPU must respect.
Q82.What is a Memory Model (e.g., JMM or C++11 model), and why do we need one?
JMM or C++11 model), and why do we need one?A memory model is the contract, defined by a language or hardware, that specifies which values a read is allowed to see in the presence of concurrent writes, and what ordering and visibility guarantees synchronization gives you. We need one because without shared, portable rules, correct multithreaded code would be impossible to write against unpredictable compiler and CPU reordering.
What it defines:
The happens-before rules that establish visibility and ordering between threads.
Which operations are atomic and what synchronization primitives (volatile, atomics, locks) mean.
The semantics of a data race, typically declaring it undefined behavior (C++) or a specific weak outcome (Java).
Why we need it:
It abstracts away diverse hardware (x86, ARM) so the same program is correct everywhere.
It tells compilers which optimizations are legal and gives programmers a precise standard to reason about concurrency.
Examples: The Java Memory Model (JMM) and the C++11 memory model, the latter offering explicit orderings like memory_order_acquire and memory_order_release.
Q83.What is the difference between Sequential Consistency and Eventual Consistency in a multi-threaded program?
Q84.What is a memory barrier, and how does it enforce ordering between different threads?
Q85.What is a 'Memory Barrier' (or Fence), and what is the difference between Acquire and Release semantics?
Q86.What is safe publication of an object, and why can sharing a reference across threads without proper synchronization expose a partially constructed object?
Q87.What is sequential consistency as a memory model guarantee, and why don't most real systems provide it by default?
Q88.How does the CAS (Compare-and-Swap) operation work? Why is it considered the building block of lock-free programming?
CAS (Compare-and-Swap) operation work? Why is it considered the building block of lock-free programming?Q89.Explain the ABA problem in lock-free data structures, and how do tagged pointers or hazard pointers solve it?
Q90.What is the difference between Lock-free, Wait-free, and Obstruction-free algorithms?
Q91.Why is it generally harder to implement a lock-free Queue than a lock-free Stack?
Queue than a lock-free Stack?Q92.Why are lock-free data structures often more performant under high contention than lock-based ones?
Q93.What are the differences between test-and-set, fetch-and-add, and compare-and-swap as atomic primitives?
test-and-set, fetch-and-add, and compare-and-swap as atomic primitives?Q94.What is Read-Copy-Update (RCU), and in what read-heavy scenarios is it beneficial?
Q95.Why is designing correct lock-free data structures so difficult compared to using locks?
Q96.Why can a striped atomic accumulator (like a LongAdder) outperform a single atomic counter under high contention?
LongAdder) outperform a single atomic counter under high contention?Q97.Conceptually, what is the C10K problem, and how did the shift from 'thread-per-connection' to 'event-driven' architectures solve it?
Q98.What happens under the hood when a program encounters an await keyword, and how is the execution state preserved?
await keyword, and how is the execution state preserved?Q99.How is cancellation and timeout handling coordinated in asynchronous or task-based code, and why is cooperative cancellation preferred over forceful termination?
Q100.What are the Reactor and Proactor patterns, and how do they differ in handling I/O readiness versus completion?
Q101.Explain the work-stealing algorithm. How does it improve resource utilization compared to a simple work-sharing approach?
Q102.What is 'Thread Pinning' in the context of virtual threads, and why is it a performance concern?
Q103.What is M:N (many-to-many) threading, where many user-mode threads are multiplexed onto fewer OS threads, and what advantages does it give a runtime?
M:N (many-to-many) threading, where many user-mode threads are multiplexed onto fewer OS threads, and what advantages does it give a runtime?