Concurrency Interview Guide: Threads, Locks, Race Conditions & Deadlocks
From CPU Scheduling to Lock-Free Data Structures: Master the Questions That Separate Senior Engineers From the Rest

What You Must Master to Clear This Track
- Know the exact cost difference between a process context switch and a thread context switch, and why it matters for scheduler design.
- Memorize the 4 necessary conditions for deadlock (Coffman conditions) and be able to name a concrete prevention strategy for each.
- Be able to implement a bounded producer-consumer queue from scratch using a lock and condition variable, not just describe it.
- Understand the happens-before relationship and why volatile alone does not make a compound operation like i++ thread-safe.
- Distinguish lock-free from wait-free algorithms, and explain the ABA problem in compare-and-swap based structures.
Step-by-Step Study Plan
Follow this sequential roadmap designed to take you from core foundations to advanced architecture and mock interviews.
Processes, Threads & the Locking Toolbox
Process vs thread memory model, CPU scheduling, thread lifecycle, and the core primitives: mutex, semaphore, monitor, and read-write locks.
- •Explain why thread context switches are cheaper than process context switches (no TLB flush, shared address space).
- •Implement a critical section using a mutex and reason about mutual exclusion, progress, and bounded waiting.
- •Differentiate binary semaphores (signaling, no ownership) from mutexes (ownership, priority inheritance).
- •Draw the 5-state thread lifecycle diagram (New, Runnable, Blocked, Waiting, Terminated) from memory.
- •Trace through a round-robin scheduler with a fixed quantum on paper for 3 competing threads.
Diagnosing and Preventing Concurrency Failures
Race condition anatomy, the 4 necessary conditions for deadlock, prevention/avoidance/detection strategies, and the classic producer-consumer and dining philosophers problems.
- •Reproduce a two-thread, two-lock deadlock on a whiteboard and identify which of the 4 Coffman conditions to break.
- •Implement the producer-consumer pattern with a bounded buffer, blocking correctly on full/empty conditions.
- •Explain lock ordering, try-lock with timeout, and the Banker's Algorithm as three distinct deadlock-avoidance strategies.
- •Practice tracing thread interleavings for a shared counter increment to spot the exact race window.
- •Rewrite the dining philosophers solution using resource (lock) ordering instead of a waiter/arbitrator.
Visibility Guarantees & High-Throughput Concurrency Patterns
Happens-before relationships, volatile vs atomic vs synchronized, compare-and-swap, lock-free/wait-free data structures, and thread pool design.
- •Explain why volatile guarantees visibility but not atomicity, and when that distinction breaks production code.
- •Implement a lock-free counter using compare-and-swap (CAS) and describe the ABA problem and its fix (versioned references).
- •Size a thread pool correctly for CPU-bound vs I/O-bound workloads using the utilization formula.
- •Mock-interview yourself explaining the JMM happens-before rules for volatile writes, lock releases, and thread starts.
- •Compare a lock-based queue against a lock-free ring buffer under contention and articulate the trade-offs out loud.
1. Processes vs. Threads & CPU Scheduling
Every concurrency interview starts by testing whether you understand what the operating system is actually juggling underneath your code: isolated processes with their own address space, versus lightweight threads that share one.
A process owns its own virtual address space, file descriptors, and heap. Switching between processes requires flushing the Translation Lookaside Buffer (TLB) and swapping page tables, which is expensive due to cache and TLB misses.
Threads within a process share the heap, global data, and file handles, but each gets its own stack, program counter, and register set. Thread switches only save/restore registers, making them far cheaper than process switches.
Preemptive schedulers (Round Robin with a fixed time quantum, Completely Fair Scheduler in Linux using a red-black tree of virtual runtimes) forcibly interrupt running threads; cooperative schedulers rely on threads yielding voluntarily.
New -> Runnable (ready or actively executing) -> Blocked (waiting on I/O or a lock) -> Waiting/Timed Waiting (waiting on a condition or join) -> Terminated. Interviewers probe whether you know Blocked and Waiting are distinct states.
- When asked to size a thread pool, quote the formula: Threads = N_cpu * U_target * (1 + Wait/Compute), and explain that I/O-bound work justifies a much larger pool than CPU-bound work.
- If asked 'why not just spawn a thread per request', bring up per-thread stack memory cost (typically 512KB-1MB) and OS scheduler overhead at high thread counts, then pivot to thread pools or event loops.
- Saying threads are 'always faster' without acknowledging that CPU-bound work on more threads than cores adds context-switch overhead, not speed.
- Confusing concurrency (interleaved progress, possible on one core) with parallelism (simultaneous execution, requires multiple cores).
2. Synchronization Primitives: Mutex, Semaphore, Monitor & Read-Write Locks
Once you can name the actors (processes and threads), interviewers pivot to the tools that make shared state safe. Expect to be asked to implement, not just describe, at least one of these live.
Binary, ownership-based lock: only the thread that acquired it may release it. Most runtimes support reentrancy (ReentrantLock in Java) so the owning thread can re-acquire without deadlocking itself.
Maintains an integer permit count; any thread can signal (release) regardless of which thread acquired. Used to bound concurrent access to a pool of N identical resources (e.g. a connection pool of size 10).
A monitor bundles a lock with one or more condition variables (wait/notify/notifyAll or await/signal) so a thread can atomically release the lock and block until a predicate becomes true, then re-acquire before proceeding.
Allows unlimited concurrent readers OR one exclusive writer, never both. Ideal for read-heavy caches, but naive implementations can starve writers if reads keep arriving; fair variants queue new readers behind a waiting writer.
public class BoundedBuffer<T> {
private final Queue<T> queue = new ArrayDeque<>();
private final int capacity;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public BoundedBuffer(int capacity) {
this.capacity = capacity;
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // release lock, block until space frees up
}
queue.offer(item);
notEmpty.signal(); // wake one waiting consumer
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // release lock, block until an item arrives
}
T item = queue.poll();
notFull.signal(); // wake one waiting producer
return item;
} finally {
lock.unlock();
}
}
}- If an interviewer asks 'mutex vs semaphore', lead with ownership: a mutex has an owner and only that owner can unlock it; a binary semaphore has no owner concept and any thread can signal it, which is why semaphores double as signaling mechanisms between threads.
- Using if instead of while around a condition variable's await() call, which breaks correctness the moment there is more than one waiter.
- Forgetting to unlock in a finally block, so an exception thrown inside a critical section leaves the lock permanently held.
3. Race Conditions & the Anatomy of Deadlocks
This is the section where interviewers separate candidates who have memorized vocabulary from those who can actually reason about interleavings. Expect a live whiteboard trace of a deadlock forming between two threads.
Occurs when the correctness of a result depends on the non-deterministic timing/interleaving of two or more threads accessing shared state, at least one of them writing. A classic example: two threads executing count++ (read, increment, write) can lose an update.
A correct solution must guarantee Mutual Exclusion (only one thread inside at a time), Progress (a decision on who enters next isn't postponed indefinitely), and Bounded Waiting (a limit on how many times other threads can cut in line).
Mutual Exclusion (resources aren't shareable), Hold and Wait (a thread holds one resource while waiting for another), No Preemption (a resource can't be forcibly taken away), and Circular Wait (a cycle of threads each waiting on the next). All four must hold simultaneously for deadlock to occur.
Break Hold-and-Wait by acquiring all locks upfront; break Circular Wait via a strict global lock ordering; break No-Preemption with try-lock-and-backoff/timeouts; or use the Banker's Algorithm to avoid ever entering an unsafe state by simulating resource allocation before granting it.
The classic circular-wait sequence: two threads each acquire one lock and then block waiting for the other's lock, with no path forward for either.
- When asked to fix the two-account transfer deadlock above, the textbook answer is to always acquire locks in a fixed global order (e.g. by account ID ascending), which eliminates circular wait entirely regardless of transfer direction.
- Distinguish deadlock (all involved threads are permanently blocked) from livelock (threads keep changing state in response to each other but make no progress, e.g. two people repeatedly stepping aside in the same direction) from starvation (a thread is perpetually denied a resource due to unfair scheduling, but other threads do make progress).
- Naming only 2 or 3 of the 4 Coffman conditions, which signals memorization gaps under interview pressure.
- Proposing 'just add more logging' or 'just restart the service' as a deadlock fix instead of addressing lock ordering or acquisition strategy.
- Confusing a database deadlock (two transactions holding row locks and waiting on each other, detected by a wait-for graph) with an OS-level deadlock, when in fact the underlying circular-wait mechanism is identical.
4. Memory Models, Lock-Free Structures & Practical Concurrency Patterns
Senior and staff-level rounds push past locking into visibility guarantees and high-throughput designs: what does another thread actually see after a write, and how do you avoid locks altogether under extreme contention?
The Java Memory Model (and equivalents in C++11/Rust) define happens-before edges: a write to a volatile field happens-before every subsequent read of that field; unlocking a monitor happens-before a later thread locking the same monitor; thread.start() happens-before any action in the started thread.
volatile guarantees visibility and prevents instruction reordering around it, but not atomicity of compound operations (i++ on a volatile int is still a data race). Atomic classes (AtomicInteger) use CAS loops for lock-free atomic read-modify-write. synchronized gives both visibility and mutual exclusion at the cost of blocking.
CAS atomically updates a memory location only if it still holds an expected value. The ABA problem occurs when a value changes from A to B and back to A between a thread's read and its CAS, fooling the CAS into succeeding incorrectly; fixed with versioned/tagged references (AtomicStampedReference).
Lock-free guarantees system-wide progress (some thread always completes) even if individual threads retry; wait-free guarantees every thread completes in a bounded number of steps, a much stronger and rarer guarantee. Thread pools (fixed, cached, work-stealing ForkJoinPool) amortize thread-creation cost and cap concurrency.
public class LockFreeCounter {
private final AtomicInteger value = new AtomicInteger(0);
public int incrementAndGet() {
while (true) {
int current = value.get();
int next = current + 1;
// CAS retries automatically if another thread updated 'value'
// between our read and this compareAndSet call.
if (value.compareAndSet(current, next)) {
return next;
}
// else: lost the race, loop and retry with the fresh value
}
}
}- If asked to justify a lock-free design, be honest about the trade-off: lock-free structures trade worst-case fairness and simplicity for better average-case throughput and immunity to a thread dying mid-critical-section.
- For thread pool sizing questions, explicitly separate CPU-bound pools (size ~= number of cores) from I/O-bound pools (size scaled up by the wait/compute ratio, or replaced entirely by an async/event-loop model).
- Claiming volatile makes a counter thread-safe for increments; it only prevents caching stale values, it does not make read-modify-write atomic.
- Describing lock-free and wait-free as synonyms; wait-free is a strictly stronger, harder-to-achieve guarantee.
Resolving a Production Deadlock in a Payment Reconciliation Service
A fintech backend team's nightly reconciliation batch job began intermittently freezing in production, holding open database connections until a health-check killed the pod. The freezes only occurred when reconciliation for two merchant accounts ran concurrently and happened to reference each other's ledger entries in the same batch window.
- 1Pulled thread dumps during a live freeze and found two worker threads each BLOCKED waiting to acquire a row lock the other thread already held, a textbook circular wait.
- 2Traced the code path and confirmed each transfer transaction locked the source account row first, then the destination account row, with no consistent ordering between the two.
- 3Reproduced the deadlock reliably in a local test by forcing two threads to process a same-pair transfer in opposite directions concurrently.
- 4Refactored the transfer logic to always acquire row locks in ascending account-ID order regardless of transfer direction, eliminating circular wait structurally.
- 5Added a defensive lock-acquisition timeout with automatic retry-with-backoff as a second line of defense in case a future code path reintroduced inconsistent ordering.
- 6Rolled the fix out behind a feature flag to one region first and monitored lock-wait metrics before a full deployment.
Top Must-Know Interview Questions & Model Answers
Q1: What is the fundamental difference between a process and a thread, and why are thread context switches cheaper?
- •Process switches invalidate cached virtual-to-physical address translations (TLB), causing a burst of cache/TLB misses right after the switch.
- •Thread switches within the same process only need to save/restore the CPU register file, stack pointer, and program counter, since the page tables stay identical.
Q2: Walk through the 5 states in a typical thread lifecycle.
- •Blocked and Waiting are frequently confused: Blocked specifically means waiting to enter a synchronized region held by another thread, while Waiting means the thread itself chose to pause (e.g. wait(), join(), sleep()).
- •A Runnable thread is not guaranteed to be running; on a single-core machine, many threads can be Runnable while only one actually executes at a time.
Q3: Compare preemptive Round Robin scheduling to Linux's Completely Fair Scheduler (CFS).
- •Round Robin's fairness is purely by turn count, which can under-serve short interactive tasks stuck behind long CPU-bound ones if the quantum is too large.
- •CFS weights virtual runtime by a task's 'nice' priority, so lower-priority tasks accumulate virtual runtime faster and get scheduled less often, without ever fully starving.
Q4: How would you size a thread pool for a mixed CPU-bound and I/O-bound workload?
- •Oversizing a CPU-bound pool beyond core count adds pure context-switch overhead with no throughput gain.
- •Undersizing an I/O-bound pool leaves cores idle while all threads are blocked on network or disk waits.
Q5: Define a race condition and give a minimal example where it silently loses data.
- •count++ compiles to three separate steps at the bytecode/instruction level: load, add 1, store; any interleaving of these steps across threads can lose an increment.
- •The fix is to make the read-modify-write sequence atomic, either via a lock around it or an atomic CAS-based primitive.
Q6: What three properties must a correct critical-section solution guarantee?
- •Mutual exclusion alone is not sufficient; a solution can be mutually exclusive yet still starve a specific thread indefinitely if it lacks bounded waiting.
- •Progress specifically rules out solutions where threads outside the critical section can block the decision of who enters it next.
Q7: What is the difference between a mutex and a semaphore?
- •Because a mutex has ownership, some implementations support priority inheritance to avoid priority inversion; a plain counting semaphore typically does not.
- •A binary semaphore (count of 1) can look like a mutex but without the ownership check, meaning a thread that never acquired the permit can still release it, which is a common source of subtle bugs.
Q8: What is a monitor, and how do wait() and notify()/notifyAll() work inside one?
- •wait() must always be called with the lock held and inside a loop checking the condition, because the thread may wake spuriously or the condition may already be false again by the time it re-acquires the lock.
- •notify() wakes an arbitrary single waiter, which is dangerous if multiple different conditions share one monitor; notifyAll() is safer by default unless you can prove only one class of waiter exists.
Q9: When would you use a read-write lock instead of a plain mutex, and what is its main risk?
- •A naive read-write lock lets any new reader join as long as no writer currently holds the lock, which can indefinitely delay a waiting writer under heavy read load.
- •Fair implementations queue incoming readers behind an already-waiting writer, trading some read throughput for writer progress guarantees.
Q10: What is a spinlock, and when is it preferable to a blocking mutex?
- •Spinlocks waste CPU cycles while waiting, which makes them a poor choice on a single-core system or when hold times are long or unpredictable.
- •They are common inside OS kernels and low-latency systems where critical sections are just a few instructions and threads run on dedicated cores.
Q11: Name the 4 necessary conditions for deadlock and explain each briefly.
- •All four conditions must hold simultaneously for a deadlock to occur; removing any single one makes deadlock impossible in that system.
- •Most practical prevention strategies target Hold-and-Wait (acquire all locks up front) or Circular Wait (impose a global lock ordering), since Mutual Exclusion and No Preemption are often inherent to the resource type.
Q12: What's the difference between deadlock prevention, avoidance, and detection?
- •Prevention is the most conservative and can hurt resource utilization (e.g. forcing all locks to be acquired upfront even if some are rarely needed).
- •Detection is common in database engines: they maintain a wait-for graph between transactions and abort the 'victim' transaction with the least work done when a cycle is found.
Q13: Explain the Banker's Algorithm and the resource-allocation state it protects against.
- •It requires each process to declare its maximum possible resource need upfront, which limits its practicality in general-purpose systems where needs aren't known in advance.
- •On each request, the algorithm simulates granting it and runs a safety check across all processes before actually committing the allocation.
Q14: How does enforcing a global lock ordering prevent deadlock?
- •This directly breaks the Circular Wait condition, one of the 4 necessary conditions, without needing to touch Mutual Exclusion, Hold-and-Wait, or No Preemption.
- •The classic bank-transfer deadlock (Thread-1 locks A then wants B, Thread-2 locks B then wants A) is eliminated entirely if both threads instead always lock min(A,B) before max(A,B).
Q15: Distinguish deadlock, livelock, and starvation.
- •Livelock often results from overly polite retry logic, e.g. both threads detecting contention and immediately backing off and retrying in lockstep forever.
- •Starvation can occur even with zero deadlock or livelock present, simply because a scheduler or lock implementation systematically favors certain threads.
Q16: Describe the dining philosophers problem and one clean solution.
- •An alternative valid fix is introducing an arbitrator/waiter that only allows 4 of the 5 philosophers to attempt picking up forks at once, guaranteeing at least one full pair is always available.
- •This problem is a stand-in for any system where multiple actors need multiple shared, non-shareable resources at once, such as multi-account bank transfers or multi-table database locks.
Q17: How would you implement the producer-consumer pattern correctly with a bounded buffer?
- •Producers block on notFull.await() when the queue is at capacity and signal notEmpty after adding an item; consumers mirror this in reverse.
- •Using a single condition variable for both roles works but is less efficient, since a signal may wake a thread of the wrong type, which then has to go back to waiting.
Q18: What is the happens-before relationship in the Java Memory Model (or equivalent in C++/Rust)?
- •Concrete happens-before edges include: a write to a volatile field happens-before a subsequent read of it; unlocking a monitor happens-before the next thread's lock of that same monitor; and thread.start() happens-before any statement in the new thread.
- •This is why a plain (non-volatile, non-synchronized) shared boolean flag can cause an infinite loop in another thread even after it is set to true, since there is no guaranteed visibility edge.
Q19: Why doesn't marking a counter volatile make count++ thread-safe?
- •volatile is correct for a simple flag pattern (one thread writes, others only read) but insufficient the moment the field is both read and written based on its own prior value by multiple threads.
- •The correct fix is AtomicInteger.incrementAndGet() (CAS-based) or wrapping the increment in a lock, both of which make the whole read-modify-write sequence indivisible.
Q20: Compare volatile, AtomicInteger, and synchronized for protecting shared state.
- •Use volatile for simple state flags read by multiple threads and written by one; use Atomic classes for single counters/references updated concurrently; use synchronized/locks when multiple variables must be updated together consistently.
- •CAS-based atomics scale better than locks under moderate contention because there's no thread parking/waking, but they can degrade to wasted retries under very high contention, whereas a lock simply queues waiters.
Q21: What is instruction/memory reordering, and why can it break naively 'obvious' concurrent code?
- •The classic example is the double-checked locking anti-pattern for lazy singleton initialization: without a volatile reference, another thread can observe a partially-constructed object because the constructor's writes and the reference assignment can be reordered.
- •Memory barriers/fences (implied by volatile, locks, and atomics) prevent specific classes of reordering across the barrier, restoring the guarantees concurrent code depends on.
Q22: What is compare-and-swap (CAS), and how does it enable lock-free programming?
- •Because CAS is a single atomic hardware instruction, there is no window where another thread can observe or interfere with a half-completed update.
- •CAS-based retry loops guarantee that at least one contending thread always makes progress (lock-free progress), even though a specific unlucky thread could in theory retry many times.
Q23: Explain the ABA problem in CAS-based algorithms and how it's fixed.
- •A concrete failure case is a lock-free stack: Thread-1 reads the top node as A, gets preempted; Thread-2 pops A, pops the next node, then pushes A back on top; Thread-1 resumes and its CAS succeeds even though the stack's internal structure underneath A has completely changed.
- •Adding a version stamp that increments on every modification means the CAS only succeeds if both the reference and the stamp match, detecting the intermediate A -> B -> A cycle.
Q24: What is the difference between lock-free and wait-free algorithms?
- •Most practical 'lock-free' library structures (like java.util.concurrent.ConcurrentLinkedQueue) are lock-free, not wait-free, because CAS retry loops can theoretically starve one thread while others repeatedly win the race.
- •Wait-free algorithms typically require more complex helping schemes, where a thread that would otherwise retry instead helps complete another thread's pending operation first.
Q25: How does work-stealing improve thread pool utilization compared to a single shared task queue?
- •A worker pushes/pops its own tasks from one end of its local deque (no contention in the common case) and idle workers steal from the opposite end of another thread's deque when they run out of work.
- •This dramatically reduces lock contention compared to a single global queue, especially for fine-grained divide-and-conquer workloads that spawn many small subtasks.
Q26: How does a database deadlock differ mechanically from the classic OS-level deadlock?
- •Two-phase locking (2PL), where a transaction acquires all locks before releasing any, guarantees serializability but makes deadlock detection-and-rollback the practical strategy since lock needs aren't known upfront.
- •The aborted victim transaction is rolled back and typically retried by the application, which is why application code touching multiple tables/rows should be written to tolerate transient deadlock rollback errors.
Q27: What is false sharing, and how does it silently degrade concurrent performance?
- •A common trigger is an array of per-thread counters packed tightly together, where thread 0's counter and thread 1's counter land in the same 64-byte cache line.
- •The fix is padding each hot field to its own cache line (@Contended in Java, manual padding in C++) so unrelated writes don't ping-pong the cache line between cores.
Mistakes That Sink Otherwise Strong Candidates
Why it happens: Developers assume the thread wakes only when the condition is truly satisfied, forgetting spurious wake-ups and the fact that another thread may have already consumed the resource between the signal and this thread re-acquiring the lock.
The fix: Always wrap the wait() call in a while loop that re-checks the exact predicate before proceeding, never a one-time if check.
Why it happens: volatile does prevent stale cached reads, so it 'feels' like it should fix a shared counter, but it says nothing about atomicity of the underlying read-modify-write sequence.
The fix: Use AtomicInteger/AtomicLong (CAS-based) for simple counters, or a lock for anything spanning multiple variables that must update together.
Why it happens: Different features or team members write independent code paths that each lock the same two resources but in a different order based on local convenience, unaware of the global interaction.
The fix: Establish and document a single canonical lock-acquisition order (e.g. by resource ID) across the entire codebase, and enforce it in code review or via a lint rule.
Why it happens: The happy-path unlock() call is added right after the critical section without considering that an exception thrown mid-section skips it entirely.
The fix: Always pair lock() with a try/finally (or use try-with-resources equivalents) so unlock() executes on every code path, including exceptions.
Why it happens: Teams copy a 'reasonable-looking' pool size from another service without checking whether their workload is CPU-bound or I/O-bound.
The fix: Explicitly classify the workload and size CPU-bound pools near the core count, and I/O-bound pools using the wait/compute ratio formula, then load-test to confirm.
Why it happens: The term 'lock-free' sounds like it means no coordination is required, but it actually still requires careful CAS-based coordination and reasoning about memory ordering.
The fix: Treat lock-free code with at least as much scrutiny as lock-based code; verify it with stress tests under real contention, not just single-threaded correctness tests.
Why it happens: A CAS check that only compares a reference's identity looks correct in isolation but misses the case where the underlying node was freed, reused, and coincidentally ends up at the same address.
The fix: Use a versioned/stamped reference (e.g. AtomicStampedReference) or hazard pointers so CAS validates both identity and a change counter.
Why it happens: Under time pressure, adding a sleep() 'to fix a race condition' appears to work because it changes the interleaving probability, giving false confidence.
The fix: Use proper tools: thread dumps, race detectors (ThreadSanitizer, Java's jstack/jcmd), and stress tests with high thread counts and no artificial delays, to find and fix the actual ordering dependency.
Quick-Reference Cheat Sheet
Recommended Practice Quizzes on QuizCluster
Test your retention and prepare for timed live coding and MCQ technical screening rounds:
OS, Concurrency & Thread Safety
Drill deadlock detection, lock types, memory visibility, and thread-safety MCQs at interview pace.
Java & Spring Boot Core Assessment
Apply java.util.concurrent primitives, ExecutorService tuning, and JMM visibility rules in realistic scenarios.
Frequently Asked Questions
Do I need to write actual multi-threaded code in a concurrency interview, or just discuss concepts?
Both. Expect at least one live-coding segment (commonly producer-consumer, a thread-safe counter, or a simple thread pool) plus verbal reasoning about deadlock scenarios, memory visibility, and trade-offs between locking strategies.
Is it acceptable to say 'I would just use a ConcurrentHashMap / higher-level library' instead of implementing raw locks?
Yes for production code judgment, but interviewers still expect you to explain what the library does internally (e.g. lock striping, CAS) and to be able to implement the primitive version by hand when explicitly asked to demonstrate fundamentals.
How deep does memory model knowledge need to go for a mid-level backend interview?
You should confidently explain volatile vs synchronized vs Atomic and the happens-before relationship for common cases (volatile writes, lock release/acquire, thread start/join). Deep CPU-level memory barrier semantics are usually reserved for staff/principal-level or infrastructure-team interviews.
What's the single highest-leverage topic to prepare if I only have a few days?
The 4 necessary conditions for deadlock plus one concrete prevention strategy for each, and the producer-consumer pattern implemented from scratch with a lock and condition variable. These two show up, in some form, in nearly every concurrency round.