Loading LLD design...
Design a thread-safe blocking queue implementing the classic producer-consumer pattern. The queue provides blocking operations (put blocks when full, take blocks when empty) and timed operations (offer/poll with timeout).
Implement three variants: (1) ArrayBlockingQueue — bounded circular buffer with a single lock and two Conditions (notFull for producers, notEmpty for consumers), using while-loops to guard against spurious wake-ups; (2) LinkedBlockingQueue — optionally bounded linked list with TWO separate locks (putLock + takeLock) allowing concurrent put and take, with AtomicInteger count for cross-lock coordination; (3) PriorityBlockingQueue — unbounded min-heap where take blocks when empty but put never blocks, dequeuing items in priority order. All variants enforce non-null items and provide peek, size, isEmpty, and bulk drain operations.
put() — blocking insert
Block the producer thread until space is available, then insert at tail of the circular buffer
take() — blocking remove
Block the consumer thread until an item is available, then remove and return from head
offer() — timed insert
Try to insert with an optional timeout; return false if queue remains full after timeout
poll() — timed remove
Try to remove with an optional timeout; return null if queue remains empty after timeout
Circular array buffer
Fixed-size array with head and tail indices wrapping via modulo; O(1) enqueue and dequeue
Two-Condition signalling
notFull Condition wakes producers; notEmpty Condition wakes consumers — avoids unnecessary wake-ups vs single Condition
Two-lock linked variant
LinkedBlockingQueue with separate putLock and takeLock allows concurrent put+take; AtomicInteger count coordinates
Priority blocking queue
Unbounded queue backed by a min-heap; take blocks when empty, put never blocks; items dequeued in priority order
Bulk operations
drain_to() transfers multiple items atomically; clear() removes all and wakes waiting producers
Before diving into code, clarify the use cases and edge cases. Understanding the problem deeply leads to better class design.
Identify the primary actions users will perform. For a parking lot: park vehicle, exit vehicle, check availability. Each becomes a method.
Who interacts with the system? Customers, admins, automated systems? Each actor type may need different interfaces.
What are the limits? Max vehicles, supported vehicle types, payment methods. Constraints drive your data structures.
What happens on overflow? Concurrent access? Payment failures? Thinking about edge cases reveals hidden complexity.