A GitHub job opening landed in my inbox
Today I opened my inbox and saw a GitHub job opening — the first time I’d ever seen one, so it felt a bit novel, and I clicked in to take a look. Since I’ve been playing with DeepSeek Harness, I handed it the job description, told it about a few of my past roles, and asked it to write me a coding interview question.
The question:
2026-08-18 · Bounded Blocking Queue
Background
ArrayBlockingQueue / LinkedBlockingQueue in java.util.concurrent are industrial-grade implementations. Interviewers love asking candidates to “write your own blocking queue”, because it tests everything at once: choice of concurrency primitives, memory visibility, lock granularity, lost wakeups, interruption semantics, and fairness.
Basic requirements
- Do not use the JDK’s built-in
BlockingQueue,ArrayBlockingQueue,LinkedBlockingQueue,ConcurrentLinkedQueue, and so on — write it from scratch. - Implement the following interface (you may turn it into an abstract class; just keep the semantics):
public interface BoundedQueue<T> {
void put(T item) throws InterruptedException; // blocks when the queue is full
T take() throws InterruptedException; // blocks when the queue is empty
int size(); // current element count
boolean isEmpty(); // whether it is empty
}
- Bounded: take a
capacity > 0in the constructor;putblocks when full,takeblocks when empty. - Thread-safe: correct under multiple producers + multiple consumers — no lost data, no duplicates, no returning
null, no going out of bounds, andsize()never negative or over capacity. - Genuinely blocking, no busy-wait:
while (!condition) Thread.sleep(...)or bare spinning doesn’t count. - Interruption semantics: when interrupted while blocked, throw
InterruptedExceptionand restore the interrupt flag (Thread.currentThread().interrupt()).
Advanced (if you’re up for it)
- Timeout versions:
boolean offer(T item, long timeout, TimeUnit unit)andT poll(long timeout, TimeUnit unit). - Fairness: wake the longest-waiting thread first (think of
ReentrantLock(true)’s fair-queue idea). - Dual-lock version: can you split it into a put lock + take lock like
LinkedBlockingQueuefor higher throughput? And what’s the cost? - Thought question (no code): why does
ArrayBlockingQueueuse one lock + two Conditions, whileLinkedBlockingQueueuses two locks? When is each appropriate?
Scoring dimensions
| Dimension | What it looks at |
|---|---|
| Correctness | Is it really safe under concurrency? Is memory visibility (volatile / lock) handled right? |
| Blocking / wakeup | wait/notify or Lock/Condition? Did you write the while-loop condition check (to avoid spurious wakeups / lost wakeup)? |
| Interruption | Does it propagate InterruptedException correctly and restore the interrupt flag? |
| Performance | Lock granularity; can size()/isEmpty() be read lock-free? |
| Testing | How do you prove it’s correct? Multithreaded stress tests, CountDownLatch assistance, even jcstress? |
Me
Good question, honestly — and I genuinely couldn’t do it anymore. AI coding has been my downfall.
I first tried hand-writing it in Notepad. When I got to Runnable, I wavered for a second — is that in the java.util package? … no, it’s in java.lang, right…
This won’t do. I’m not ready to retire just yet.
Still, do the problem first — even if it’s copying it down
public class BoundedQueueImpl<T> Implements BoundedQueue<T> {
private final Object[] items; //use final: the array reference can't be reassigned, but the contents can still change
private final Object lock = new Object(); //a private member as the lock (vs. `this`) at least avoids strangers locking on it without you knowing who did it
private int putIndex; //where the next enqueued element goes in the array
private int takeIndex; //where the next dequeued element comes from in the array
private int count; //how many elements are currently in the array
private final int capacity; //I still like declaring it explicitly
public BoundedQueueImpl(int capacity) {
if(capacity <= 0) throw new IllegalArgumentException("Capacity should be > 0");
this.capacity = capacity;
}
@Override
public void put(T item) throws InterruptedException {
synchronized(lock) {
while(count == capacity) {//full — pitfall #1: can't use `if`, would overflow
lock.wait();
}
item[putIndex] = item;
putIndex = (putIndex +1) % capacity;
count++;
lock.notifyAll(); //pitfall #2: can't be `notify`, would deadlock
}
}
@Override
@SuppressWarning("unchecked")
public T take() throws InterruptedException {
synchronized(lock) {
while(count == 0) {//empty — same as pitfall #1: can't use `if`, would underflow
lock.wait();
}
T item = (T)items[takeIndex];
items[takeIndex] = null;//good habit: allow early GC
takeIndex = (takeIndex+1) % capacity;
count--;
lock.notifyAll();//again, notifyAll
return item;
}
}
@Override
public int size() {
synchronized(lock) {//can't omit this — omitting it invites bugs: instruction reordering, consistency...
return count;
}
}
@Override
public boolean isEmpty() {
synchronized(lock) {
return count == 0;
}
}
}
AI review
Four bugs (three compile errors + one fatal omission)
| Where | Problem | Fix |
|---|---|---|
| Constructor | items was never new’d — private final Object[] items; but the constructor never does items = new Object[capacity];, so a final field is left uninitialized → won’t compile; even if it did, the first put would NPE. This is the only logic-level one. |
Add items = new Object[capacity]; in the constructor. |
| put | item[putIndex] = item; — used the parameter name item as if it were the array |
items[putIndex] = item; |
| Class declaration | Implements is capitalized |
implements |
| Annotation | @SuppressWarning |
@SuppressWarnings |