DEV Community

Machine coding Master
Machine coding Master

Posted on Originally published at javalld.com

Java Concurrency LLD: Build a Custom BlockingQueue From Scratch

Java Concurrency LLD: Build a Custom BlockingQueue From Scratch

At Apple and Amazon, implementing a thread-safe BlockingQueue is one of the most frequent concurrency challenges in Low-Level Design (LLD) interviews. It immediately exposes whether you understand thread coordination, thread-safe state mutation, and JVM internals, or merely rely on off-the-shelf utilities.

The Mistake Most Candidates Make

  • Relying on intrinsic locks (synchronized) with notifyAll(), which triggers the "thundering herd" problem by waking up both producers and consumers simultaneously.
  • Checking capacity bounds using an if statement instead of a while loop, leaving the queue vulnerable to state corruption via spurious wakeups.
  • Failing to structure lock acquisition with a proper try-finally block, which causes unrecoverable deadlocks if a thread encounters an unexpected exception or interruption.

The Right Approach

  • Core mental model: Decouple producer and consumer wait states using an explicit lock tied to two independent condition queues.
  • Key entities/classes: ReentrantLock, Condition (notFull, notEmpty), array-based circular buffer (Object[]).
  • Why it beats the naive approach: Direct signaling via signal() wakes only the specific thread capable of making progress (producers wake consumers and vice versa), drastically reducing context switching.

Want to go deeper? javalld.com — machine coding interview problems with working Java code and full execution traces.

The Key Insight (Code)

public void put(E item) throws InterruptedException {
    lock.lockInterruptibly();
    try {
        while (count == items.length) {
            notFull.await();
        }
        enqueue(item);
        notEmpty.signal();
    } finally {
        lock.unlock();
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Dual Conditions, Single Lock: Bind two separate Condition instances (notFull, notEmpty) to one ReentrantLock to isolate wait-sets for producers and consumers.
  • Always Guard with while: Always check wait conditions inside a while loop to re-verify state invariants after waking up from await().
  • Symmetric Cross-Signaling: Modifying the queue must notify the opposite condition—producers signal notEmpty after adding an element, and consumers signal notFull after removing one.

Full working implementation with execution trace available at https://javalld.com/learn/blocking-queue

Top comments (0)