Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
Chaturmind
← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
HomeLearnJava Interview PrepJava Interview Prep: 8+ Years (Senior & Lead)Collections & Concurrency at Scale
✓ FreeAdvanced· 12 min read

Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions

Race conditions, deadlock vs livelock with real examples, detecting deadlocks (thread dumps, ThreadMXBean, JFR) and debugging them in production, starvation and why thread priorities are a poor fix, preventing livelock, refactoring ConcurrentModificationException in multithreaded code, designing concurrent access to a shared resource, a deadlock-critical banking transfer design, high-concurrency design principles, and handling 10K concurrent booking requests without overselling.

Published September 25, 2026


How to use this lesson

These are design and incident questions. Show:

  • a prevention strategy (lock ordering, timeouts, no shared state);
  • a detection strategy (thread dumps, JFR, metrics);
  • that in distributed systems, the "shared resource" is usually a database row, not a Java object.

Q1. What is a race condition?

Short answer: A bug where the result depends on the timing and interleaving of threads that access shared state without proper synchronisation. The two classic forms:

  • Read-modify-write: count++, where two threads read 5, and both write 6.
  • Check-then-act: if (!map.containsKey(k)) map.put(k, v), or "if seats are available, book one", where both threads pass the check.

It's non-deterministic: it passes tests, and fails under load. The fixes: make the operation atomic (locks, atomics, computeIfAbsent, database constraints or conditional updates), or remove the sharing (immutability, confinement).

Learn it in depth → Deadlock, Starvation, Livelock

Q2. What's the difference between deadlock and livelock?

Short answer:

  • Deadlock: threads are blocked forever, each waiting for a resource another holds. They're not running: CPU is near 0, and the threads show as BLOCKED or WAITING. It needs all four Coffman conditions: mutual exclusion, hold-and-wait, no preemption, and a circular wait.
  • Livelock: threads are active, and keep changing state in response to each other, but make no progress. For example, both back off and retry in lockstep, forever. CPU is busy, and the threads are RUNNABLE.

Q3. What's a real-world example of livelock, and how would you resolve it? How do you prevent livelock?

Short answer:

  • Examples:
    • Two services each use tryLock on resources A and B, in opposite orders. Each fails the second tryLock, releases, and retries immediately. With identical timing, they collide forever.
    • The same happens with message redelivery: a poison message fails, is requeued at the head, and is retried endlessly while nothing else is processed.
    • Retry storms between two services that each retry on the other's failure.
  • Resolution and prevention:
    • Randomised exponential back-off (jitter) breaks the symmetry.
    • A consistent resource ordering removes the conflict entirely.
    • Retry limits with dead-letter queues, for poison messages.
    • Priorities or a single arbiter (only one side retries, or a coordinator assigns turns).

Q4. How would you detect a deadlock? How do you debug a deadlock in production?

Short answer:

  1. Symptoms: requests hang, the thread pools fill up, throughput drops to zero, and CPU is low.
  2. Thread dumps: jcmd <pid> Thread.print (or jstack). The JVM reports Java-level deadlocks automatically at the end ("Found one Java-level deadlock"), with the cycle of threads, the locks they hold, and the locks they want. That covers monitors and java.util.concurrent ownable locks.
  3. Programmatically: ThreadMXBean.findDeadlockedThreads(). Run it periodically, expose it as a health indicator or metric, and alert on it.
  4. JFR/JMC for lock contention, and for threads blocked a long time.
  5. The fix: find the two code paths acquiring the locks in opposite orders (from the stack traces). Then introduce a global order, reduce the scope so only one lock is held, use tryLock with a timeout, or redesign to avoid nested locks. Restart the affected instance to recover in the meantime.

Key points to cover:

  • The automatic detection doesn't see:
    • deadlocks through database locks (the database detects those, and aborts a victim with a deadlock error, so check the database logs);
    • distributed deadlocks between services;
    • resource starvation (a pool deadlock: tasks waiting on other tasks in the same exhausted pool).
ThreadMXBean mx = ManagementFactory.getThreadMXBean();
long[] ids = mx.findDeadlockedThreads();
if (ids != null) for (ThreadInfo info : mx.getThreadInfo(ids, true, true)) log.error("DEADLOCK: {}", info);

Q5. What is thread starvation? How do thread priorities affect it, and how do you prevent it?

Short answer: Starvation is a thread that's perpetually denied the resources it needs (the CPU, a lock, a pool slot), so it makes no progress, even though the system as a whole does. The causes:

  • Non-fair locks: barging threads keep winning.
  • Long-held locks.
  • Readers starving writers (read/write locks).
  • Thread-pool starvation: long or blocking tasks occupy every worker, or tasks wait on subtasks queued behind them in the same bounded pool (a pool-induced deadlock).
  • Greedy work loops.
  • Priorities (see below).

Thread priorities (setPriority 1–10) are hints, mapped to OS priorities inconsistently, and often ignored (Linux, by default, ignores them for normal threads). Relying on them is non-portable. Lowering or raising priorities can cause starvation, not reliably prevent it.

Prevention:

  • Fair locks where fairness matters.
  • Short critical sections.
  • Separate pools (bulkheads) for slow and fast work.
  • No blocking I/O in shared or common pools.
  • Never wait on tasks in the same bounded pool.
  • Bounded queues and timeouts.
  • Virtual threads for blocking-heavy work.
  • Monitor the queue wait times.

Q6. During a code review, you find a ConcurrentModificationException caused by modifying a list while iterating over it, in multithreaded code. How would you refactor it?

Short answer: First, understand who owns the list, and the access pattern. Then:

  1. If only one thread should ever use it: it's a design bug. Confine the list: don't share it, and pass immutable copies (List.copyOf) to the other threads.
  2. Read-mostly, iterated often (listeners, subscribers): CopyOnWriteArrayList. Iterators work on snapshots, and are never CME.
  3. Frequent concurrent adds and removes: concurrent queues (ConcurrentLinkedQueue, ConcurrentLinkedDeque), or ConcurrentHashMap.newKeySet() for sets. They have weakly consistent iteration.
  4. You need a consistent list with compound operations: guard both iteration and modification with the same lock, or snapshot under the lock, then iterate the copy outside it.
  5. Single-threaded removal while iterating (a separate bug): use removeIf or Iterator.remove.

Common trap: Collections.synchronizedList alone doesn't fix it. You must still synchronized (list) { for (...) } around the iteration.

Q7. How would you design a Java application that handles concurrent access to a shared resource?

Short answer: Go through the options, from least to most coordination:

  1. Avoid sharing: immutable objects, thread confinement, partitioning (shard by key, so each partition has a single writer, like Kafka partitions or actors).
  2. Use proven concurrent building blocks: ConcurrentHashMap compound operations, atomics, BlockingQueue hand-offs, and executors.
  3. Lock when you must: one lock per invariant, short critical sections, a consistent lock order, tryLock with timeouts, and read/write or stamped locks for read-heavy data.
  4. Limit concurrency to external resources with semaphores or bulkheads, and connection pools sized to the downstream's capacity.
  5. Across JVMs, the shared resource is usually in a database or cache. Use database transactions, optimistic locking (@Version), conditional updates, unique constraints, or distributed locks (with fencing tokens) sparingly.
  6. Observe and test: contention metrics, JFR, and concurrency stress tests.

Q8. Design a multithreaded application where avoiding deadlock is critical.

Short answer: A banking funds-transfer engine, with many threads transferring between accounts.

  • The risk: thread 1 locks A then waits for B, while thread 2 locks B then waits for A. That's a deadlock, and payments freeze.
  • The design:
    • Global lock ordering: always lock the account with the lower ID first, whatever the transfer direction. That removes circular waits.
    • Short critical sections: compute and validate outside the locks, and hold them only for the debit and credit.
    • Timeouts: tryLock(timeout), with a clean failure or retry and jitter, as a safety net.
    • An audit trail and idempotency keys, so retries don't double-post.
    • In reality, the source of truth is the database. Use a single transaction with SELECT … FOR UPDATE in ID order (or optimistic versions), and let the database's deadlock detection plus a retry on deadlock errors handle the rest.
    • An alternative architecture: partition accounts by ID to single-threaded processors (the actor or LMAX style), so there are no locks at all. Cross-partition transfers become two-phase messages, or sagas.
void transfer(Account a, Account b, BigDecimal amt) {
    Account first = a.id() < b.id() ? a : b, second = first == a ? b : a;
    synchronized (first) {
        synchronized (second) {                 // same order for every thread, so no cycle
            a.debit(amt);
            b.credit(amt);
        }
    }
}

Q9. How would you diagnose and solve thread starvation and deadlock issues?

Short answer:

  • Diagnose them:
    • Metrics first: pool active count at the maximum, queue size growing, and task wait time rising (starvation); zero throughput with low CPU (deadlock).
    • Several thread dumps, seconds apart: deadlocks are reported explicitly. For starvation, look at what all the pool threads are doing (blocked on I/O? waiting for futures from the same pool?).
    • JFR: thread park, monitor wait, and socket-read events, with durations.
  • Solve them:
    • Deadlock: a consistent lock ordering, fewer nested locks, tryLock with timeouts, and immutable data.
    • Starvation:
      • separate executors per workload;
      • don't block pool threads on other tasks in the same pool (use CompletableFuture composition, or ForkJoin's helping joins);
      • move blocking I/O to a dedicated pool, or to virtual threads;
      • fair locks where needed;
      • time out long tasks;
      • size the pools from measurements.

Q10. What is high-concurrency design?

Short answer: Designing systems that stay correct and fast under many simultaneous requests. The principles:

  • Statelessness and horizontal scaling: push shared state to purpose-built stores.
  • Avoid contention: partition or shard the data, single-writer principles, lock-free or striped structures, optimistic concurrency instead of pessimistic locks.
  • Asynchronous and non-blocking I/O, or virtual threads, so waiting doesn't consume scarce threads.
  • Back-pressure and bounded queues, rate limiting and load shedding, so overload degrades gracefully.
  • Caching with careful invalidation. Idempotency for safe retries.
  • Batching (fewer, larger operations) and connection pooling.
  • Resilience (timeouts, bulkheads, circuit breakers) to stop cascading failures.
  • Measure: load tests, latency percentiles, contention profiling.

Q11. How would you handle 10,000 concurrent booking requests (for example, the last seats or hotel rooms) without overselling?

Short answer: Correctness comes from the data layer, and scalability from how you funnel requests.

  1. Atomic inventory decrement in the database, as a conditional update, with no read-then-write:
UPDATE inventory
SET    available = available - :qty, version = version + 1
WHERE  room_type_id = :id AND stay_date = :date AND available >= :qty;
-- rows updated = 1 → reserved; 0 → sold out (no oversell, no explicit lock)

Or use optimistic locking (@Version) with retries, or SELECT … FOR UPDATE for short transactions. For multi-night stays, update all the date rows in one transaction, in a consistent order. 2. Hot-inventory shortcut: keep counters in Redis (DECRBY, or a Lua script that checks and decrements atomically). Accept the reservation fast, then persist asynchronously through a queue, with reconciliation. 3. Two-phase booking: create a HOLD with a TTL (for example 10 minutes) → payment → CONFIRM. Expired holds release the inventory, through a scheduler or delayed queue. 4. Throttle and queue at the edge: rate limits, a virtual waiting room for flash sales, and Kafka partitioned by inventory key, so each key is processed serially, without contention. 5. Idempotency keys on booking requests, so client retries don't create duplicate bookings. 6. Scale the stateless application tier. Use virtual threads or reactive I/O for the waiting. Size the connection pools to the database's capacity, not to the request count. 7. Observe: conflict rate, hold expiries, oversell alarms (which should always be zero), and reconciliation jobs.

Learn it in depth → Design a Meeting Room / Calendar Booking System

Q12. What is the starvation problem? (The rapid-fire version)

Short answer: Some thread or task never gets the resource it needs (CPU time, a lock, a worker slot), because others keep taking it. It's caused by unfair locks, long-held locks, greedy threads, shared pools saturated by slow tasks, or priority schemes. The cures are fairness, isolation (bulkheads), short critical sections, and bounded waits.

Follow-up questions this topic invites — and their answers

Q: What is a thread-pool-induced deadlock? A: Tasks in a bounded pool submit subtasks to the same pool, and block waiting for their results. Once every worker is waiting, the subtasks sit in the queue forever. Avoid blocking waits inside the pool (compose asynchronously), use ForkJoin (helping joins), or use separate pools.

Q: Does the database protect you from deadlocks? A: It detects database lock deadlocks, and kills one transaction (for example PostgreSQL's deadlock detected, or MySQL error 1213). Your application must catch it and retry the whole transaction. It can't help with deadlocks between an application lock and a database lock (a thread holding a Java lock while waiting on a row lock held by a thread that wants the Java lock).

Q: Why is holding a lock while calling external code dangerous? A: The callback (a listener, a logging appender, a remote call) may block for a long time (starving others), or try to acquire other locks (deadlock risk). Copy the data under the lock, release it, then call out ("open calls").

Q: How do distributed locks fail, and what's a fencing token? A: Lock leases can expire while the holder pauses (a GC pause, a network delay), so two processes believe they hold the lock. A fencing token (a monotonically increasing number issued with each lock grant) lets the protected resource reject writes carrying an older token.

Previous

Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions

Next

CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

AI Tutor

Lesson: Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions

Quick actions

AI responses can be inaccurate. Verify critical information.