Chapter 13: Queues, Deques and Circular Buffers
13.07Beginner / Intermediate6 topics14 min6 interactive animations

Applications

Queues at work: the BFS frontier, task scheduling with priorities, producer-consumer buffers, a printer queue, counting recent requests and round-robin scheduling.

Learning goals

  • Explain why BFS needs a queue and DFS needs a stack
  • Pick a FIFO, priority or bounded blocking queue for a scheduling problem
  • Trace the recent-requests counter and the round-robin scheduler

Prerequisites

Queue implementationsPriority queues
13.07.1

BFS frontier

Breadth-first search explores a graph or grid level by level: all cells one step from the start, then all cells two steps away, and so on. The **frontier** (the cells discovered but not yet expanded) must be served oldest first, so it is a FIFO queue. With a stack the same loop would be depth-first search.

Because cells are expanded in order of distance, the first time BFS reaches a cell is by a shortest path. The animation runs BFS on a grid; choose “Multi-source” to start with several cells in the queue at once.

Java
1static int[][] bfsDistances(int[][] g, int sr, int sc) { // 0 = wall, 1 = open
2 int R = g.length, C = g[0].length;
3 int[][] dist = new int[R][C]; for (int[] row : dist) Arrays.fill(row, -1);
4 Deque<int[]> frontier = new ArrayDeque<>();
5 dist[sr][sc] = 0; frontier.offer(new int[]{sr, sc});
6 int[][] dirs = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
7 while (!frontier.isEmpty()) {
8 int[] cur = frontier.poll(); // the oldest discovered cell
9 for (int[] d : dirs) {
10 int r = cur[0] + d[0], c = cur[1] + d[1];
11 if (r < 0 || r >= R || c < 0 || c >= C || g[r][c] == 0 || dist[r][c] != -1) continue;
12 dist[r][c] = dist[cur[0]][cur[1]] + 1;
13 frontier.offer(new int[]{r, c});
14 }
15 }
16 return dist;
17}

BFS on a grid: shortest path and multi-source distances

Interactive · edit the input

Your input

Try

grid · 3 × 5

01234
0
0
·
▇
·
·
1
▇
·
·
·
·
2
▇
·
·
·
·
Cell being expandedReached (distance shown)WallSource

queue (front first)

(0,0)
Setup

Start at the top-left cell with distance 0.

Step 1 / 14
Java
1static int shortestPath(int[][] g) { // 4-directional moves, 0 = wall
2 int R = g.length, C = g[0].length;
3 int[][] dist = new int[R][C]; for (int[] row : dist) Arrays.fill(row, -1);
4 Deque<int[]> queue = new ArrayDeque<>();
5 // single source: (0, 0); multi-source: every cell equal to 2, all at distance 0
6 dist[0][0] = 0; queue.offer(new int[]{0, 0});
7 while (!queue.isEmpty()) {
8 int[] cur = queue.poll();
9 for (int[] d : new int[][]{{-1, 0}, {0, 1}, {1, 0}, {0, -1}}) {
10 int r = cur[0] + d[0], c = cur[1] + d[1];
11 if (r < 0 || r >= R || c < 0 || c >= C || g[r][c] == 0 || dist[r][c] != -1) continue;
12 dist[r][c] = dist[cur[0]][cur[1]] + 1; // first arrival is the shortest, because BFS goes level by level
13 queue.offer(new int[]{r, c});
14 }
15 }
16 return dist[R - 1][C - 1]; // -1 when unreachable; multi-source: max over all cells
17}

Live state

sources

1

No condition evaluated on this step
Execution trace (dry run table)Show 14 steps
StepLineStateWhat happens
1L5sources=1Start at the top-left cell with distance 0.
2L12cell=(0,0) dist=0Expand (0,0) at distance 0: reached (0,1)=1.
3L12cell=(0,1) dist=1Expand (0,1) at distance 1: reached (1,1)=2.
4L12cell=(1,1) dist=2Expand (1,1) at distance 2: reached (1,2)=3, (2,1)=3.
5L12cell=(1,2) dist=3Expand (1,2) at distance 3: reached (1,3)=4, (2,2)=4.
6L12cell=(2,1) dist=3Expand (2,1): every open neighbour is already reached or blocked.
7L12cell=(1,3) dist=4Expand (1,3) at distance 4: reached (0,3)=5, (1,4)=5, (2,3)=5.
8L12cell=(2,2) dist=4Expand (2,2): every open neighbour is already reached or blocked.
9L12cell=(0,3) dist=5Expand (0,3) at distance 5: reached (0,4)=6.
10L12cell=(1,4) dist=5Expand (1,4) at distance 5: reached (2,4)=6.
11L12cell=(2,3) dist=5Expand (2,3): every open neighbour is already reached or blocked.
12L12cell=(0,4) dist=6Expand (0,4): every open neighbour is already reached or blocked.
13L12cell=(2,4) dist=6Expand (2,4): every open neighbour is already reached or blocked.
14L16return=6The bottom-right cell is 6 steps away, the minimum, because BFS reaches cells in order of distance.
13.07.2

Task scheduling

A scheduler repeatedly picks the next task. If all tasks are equal and fairness matters, a plain FIFO queue is enough. If tasks carry priorities or deadlines, use a priority queue: poll() always returns the most urgent task, including tasks that arrived after the others.

The “Min-queue” and “Max-queue” presets of the priority queue animation show the effect: a late arrival with a better priority overtakes the values already waiting. Remember that equal priorities are not served in arrival order unless the comparator breaks ties.

Java
1record Task(String name, int priority, long arrival) {}
2
3static List<String> schedule(List<Task> tasks) {
4 PriorityQueue<Task> pq = new PriorityQueue<>(
5 Comparator.comparingInt(Task::priority).thenComparingLong(Task::arrival)); // lower number = more urgent
6 pq.addAll(tasks);
7 List<String> order = new ArrayList<>();
8 while (!pq.isEmpty()) order.add(pq.poll().name());
9 return order;
10}

PriorityQueue: offer, poll and peek on a binary heap

Interactive · edit the input

Your input

Try

The heap is empty.

int[] arr · length 0

Being comparedJust movedRoot (next to leave)

polled so far (in order)

none yet
Setup

An empty min-queue. It is a binary heap stored in an array: the children of index i are 2i+1 and 2i+2.

Step 1 / 26
Java
1Comparator<Integer> cmp = Comparator.naturalOrder(); // Comparator.reverseOrder() makes a max-queue
2PriorityQueue<Integer> pq = new PriorityQueue<>(cmp);
3pq.offer(x); // append at the end, then sift up
4Integer top = pq.poll(); // last element to the root, then sift down
5Integer next = pq.peek(); // the root only; nothing is removed

Live state

0 comparisons0 heap swaps

size

0

order

min first

No condition evaluated on this step
Execution trace (dry run table)Show 26 steps
StepLineStateWhat happens
1L2size=0 order=min firstAn empty min-queue. It is a binary heap stored in an array: the children of index i are 2i+1 and 2i+2.
2L3op=offer(5) size=1offer(5): append at the end (index 0), the only place that keeps the tree complete.
3L3op=offer(3) size=2offer(3): append at the end (index 1), the only place that keeps the tree complete.
4L3op=offer(3) child=3 parent=5The child has higher priority than its parent, so they swap.
5L3op=offer(3)Swapped: 3 moves up to index 0.
6L3op=offer(8) size=3offer(8): append at the end (index 2), the only place that keeps the tree complete.
7L3op=offer(8) child=8 parent=3The parent keeps priority over the child (ties do not swap): sift up stops.
8L3op=offer(1) size=4offer(1): append at the end (index 3), the only place that keeps the tree complete.
9L3op=offer(1) child=1 parent=5The child has higher priority than its parent, so they swap.
10L3op=offer(1)Swapped: 1 moves up to index 1.
11L3op=offer(1) child=1 parent=3The child has higher priority than its parent, so they swap.
12L3op=offer(1)Swapped: 1 moves up to index 0.
13L4op=poll() returned=1 size=3poll() returns the root 1. The last element (5) moves to the root to fill the gap, then sifts down.
14L4op=poll() node=5 better child=3The better child (3) beats the node, so they swap.
15L4op=poll()Swapped: 3 moves up to index 0.
16L3op=offer(4) size=4offer(4): append at the end (index 3), the only place that keeps the tree complete.
17L3op=offer(4) child=4 parent=5The child has higher priority than its parent, so they swap.
18L3op=offer(4)Swapped: 4 moves up to index 1.
19L3op=offer(4) child=4 parent=3The parent keeps priority over the child (ties do not swap): sift up stops.
20L4op=poll() returned=3 size=3poll() returns the root 3. The last element (5) moves to the root to fill the gap, then sifts down.
21L4op=poll() node=5 better child=4The better child (4) beats the node, so they swap.
22L4op=poll()Swapped: 4 moves up to index 0.
23L4op=poll() returned=4 size=2poll() returns the root 4. The last element (8) moves to the root to fill the gap, then sifts down.
24L4op=poll() node=8 better child=5The better child (5) beats the node, so they swap.
25L4op=poll()Swapped: 5 moves up to index 0.
26L5size=2 return=[5, 8]Heap array: [5, 8]. Only the root is guaranteed to be the best value; the rest is just heap-ordered, not sorted.
13.07.3

Producer-consumer buffers

In a producer-consumer design one thread produces work and another consumes it. A bounded ring buffer sits between them: producers enqueue until the buffer is full, consumers dequeue until it is empty. The bound keeps memory use predictable and gives *back-pressure*: a fast producer is made to wait or drop work instead of exhausting memory.

In Java use ArrayBlockingQueue (a bounded ring) with put and take, which block when the queue is full or empty. offer and poll return immediately instead. The animation shows a single-threaded model of the same ring; the real class adds locking so that several threads can share it safely.

Java
1static void pipeline() throws InterruptedException {
2 BlockingQueue<Integer> buffer = new ArrayBlockingQueue<>(4); // bounded: capacity 4
3 Thread producer = new Thread(() -> {
4 try { for (int i = 1; i <= 8; i++) buffer.put(i); buffer.put(-1); } // put blocks while full
5 catch (InterruptedException e) { Thread.currentThread().interrupt(); }
6 });
7 Thread consumer = new Thread(() -> {
8 try { for (int v; (v = buffer.take()) != -1; ) System.out.println("consumed " + v); } // take blocks while empty
9 catch (InterruptedException e) { Thread.currentThread().interrupt(); }
10 });
11 producer.start(); consumer.start();
12 producer.join(); consumer.join();
13}

Circular queue: head, tail and a size counter

Interactive · edit the input

Your input

Try

data[4] · head 0 · tail 0 · size 0

0 HT
1
2
3
Setup

An array of 4. head is where the next value leaves, tail is where the next value arrives, size counts the values.

Step 1 / 29
Java
1class RingQueue {
2 private final int[] data; private int head, tail, size;
3 RingQueue(int capacity) { data = new int[capacity]; }
4 boolean offer(int x) {
5 if (size == data.length) return false; // full
6 data[tail] = x;
7 tail = (tail + 1) % data.length; // wrap around
8 size++; return true;
9 }
10 Integer poll() {
11 if (size == 0) return null; // empty
12 int x = data[head];
13 head = (head + 1) % data.length; // wrap around
14 size--; return x;
15 }
16}

Live state

head

0

tail

0

size

0

No condition evaluated on this step
Execution trace (dry run table)Show 29 steps
StepLineStateWhat happens
1L2head=0 tail=0 size=0An array of 4. head is where the next value leaves, tail is where the next value arrives, size counts the values.
2L5head=0 tail=0 size=0 op=offer(1)offer(1): there is room.
3L6head=0 tail=0 size=1 op=offer(1)data[0] = 1.
4L7head=0 tail=1 size=1 op=offer(1)tail = (0 + 1) % 4 = 1.
5L5head=0 tail=1 size=1 op=offer(2)offer(2): there is room.
6L6head=0 tail=1 size=2 op=offer(2)data[1] = 2.
7L7head=0 tail=2 size=2 op=offer(2)tail = (1 + 1) % 4 = 2.
8L5head=0 tail=2 size=2 op=offer(3)offer(3): there is room.
9L6head=0 tail=2 size=3 op=offer(3)data[2] = 3.
10L7head=0 tail=3 size=3 op=offer(3)tail = (2 + 1) % 4 = 3.
11L11head=0 tail=3 size=3 op=poll()poll(): there is a value at head.
12L12head=0 tail=3 size=3 op=poll() returned=1x = data[0] = 1. The slot keeps its old number (shown struck out when free): only head and size decide what is live.
13L13head=1 tail=3 size=2 op=poll() returned=1head = (0 + 1) % 4 = 1. Returned 1.
14L11head=1 tail=3 size=2 op=poll()poll(): there is a value at head.
15L12head=1 tail=3 size=2 op=poll() returned=2x = data[1] = 2. The slot keeps its old number (shown struck out when free): only head and size decide what is live.
16L13head=2 tail=3 size=1 op=poll() returned=2head = (1 + 1) % 4 = 2. Returned 2.
17L5head=2 tail=3 size=1 op=offer(4)offer(4): there is room.
18L6head=2 tail=3 size=2 op=offer(4)data[3] = 4.
19L7head=2 tail=0 size=2 op=offer(4)tail = (3 + 1) % 4 = 0: past the last index, so it wraps to the start of the array.
20L5head=2 tail=0 size=2 op=offer(5)offer(5): there is room.
21L6head=2 tail=0 size=3 op=offer(5)data[0] = 5.
22L7head=2 tail=1 size=3 op=offer(5)tail = (0 + 1) % 4 = 1.
23L5head=2 tail=1 size=3 op=offer(6)offer(6): there is room.
24L6head=2 tail=1 size=4 op=offer(6)data[1] = 6.
25L7head=2 tail=2 size=4 op=offer(6)tail = (1 + 1) % 4 = 2.
26L11head=2 tail=2 size=4 op=poll()poll(): there is a value at head.
27L12head=2 tail=2 size=4 op=poll() returned=3x = data[2] = 3. The slot keeps its old number (shown struck out when free): only head and size decide what is live.
28L13head=3 tail=2 size=3 op=poll() returned=3head = (2 + 1) % 4 = 3. Returned 3.
29L16head=3 tail=2 size=3 return=[4, 5, 6]Queue contents from head: [4, 5, 6]; 0 operations rejected.
13.07.4

Printer queue

A print spooler is a FIFO queue: jobs print in submission order, new jobs join the rear, and the printer takes the front job when it is free. A job that has started is dequeued before it prints, so cancelling it later is a separate concern.

Real spoolers often add priorities or per-user fairness on top of the queue. That is the point where FIFO is replaced by a priority queue (previous lesson) or by one queue per user served round-robin (next topics).

Java
1class PrintSpooler {
2 private final Queue<String> jobs = new ArrayDeque<>();
3 void submit(String doc) { jobs.offer(doc); }
4 String printNext() {
5 String doc = jobs.poll(); // null when nothing is waiting
6 return doc == null ? "idle" : "printing " + doc;
7 }
8}

Queue: enqueue, dequeue, peek, overflow and underflow

Interactive · edit the input

Your input

Try

queue · front → rear · size 0 of 3

empty
Setup

An empty queue. Values join at the rear and leave from the front, so the oldest value is always served first (FIFO).

Step 1 / 11
Java
1class BoundedQueue {
2 private final ArrayDeque<Integer> q = new ArrayDeque<>(); private final int capacity;
3 boolean offer(int x) {
4 if (q.size() == capacity) return false; // overflow: reject
5 q.addLast(x); return true; // join at the rear
6 }
7 Integer poll() {
8 return q.pollFirst(); // front leaves first; null when empty
9 }
10 Integer peek() { return q.peekFirst(); } // front, not removed
11}

Live state

size

0

capacity

3

No condition evaluated on this step
Execution trace (dry run table)Show 11 steps
StepLineStateWhat happens
1L2size=0 capacity=3An empty queue. Values join at the rear and leave from the front, so the oldest value is always served first (FIFO).
2L5op=offer(5) size=1 rear=5addLast(5): 5 joins behind everything already waiting. Nothing else moves.
3L5op=offer(8) size=2 rear=8addLast(8): 8 joins behind everything already waiting. Nothing else moves.
4L5op=offer(3) size=3 rear=3addLast(3): 3 joins behind everything already waiting. Nothing else moves.
5L10op=peek() front=5 size=3peek() returns 5 without removing it.
6L8op=poll() returned=5 size=2pollFirst() returns 5, the value that has waited longest. The new front is 8.
7L5op=offer(7) size=3 rear=7addLast(7): 7 joins behind everything already waiting. Nothing else moves.
8L8op=poll() returned=8 size=2pollFirst() returns 8, the value that has waited longest. The new front is 3.
9L8op=poll() returned=3 size=1pollFirst() returns 3, the value that has waited longest. The new front is 7.
10L8op=poll() returned=7 size=0pollFirst() returns 7, the value that has waited longest. The queue is empty again.
11L11size=0 failed=0 return=[]Final queue (front to rear): []; 0 operations failed.
13.07.5

Recent requests

How many requests arrived in the last window time units? Keep the timestamps of recent requests in a queue. A new request joins the rear; then the front is removed while it is older than t - window. The queue size is the answer. Timestamps arrive in increasing order, so the oldest are always at the front.

Each timestamp is added once and removed at most once: O(1) amortised per request. Run the “Window 5” preset and watch old timestamps drop out as time advances; “Sparse traffic” shows the queue shrinking back to one element.

Java
1class RecentCounter {
2 private final Deque<Integer> q = new ArrayDeque<>(); private final int window;
3 RecentCounter(int window) { this.window = window; }
4 int ping(int t) {
5 q.addLast(t);
6 while (q.peekFirst() < t - window) q.pollFirst(); // too old to count
7 return q.size();
8 }
9}

Best

O(1)

Average

O(1) amortised

Worst

O(n)

Space

O(window)

Recent requests in a sliding time window

Interactive · edit the input

Your input

Try

queue of requests in [t - 5, t]

empty
Setup

Count requests whose time is within the last 5 time units (inclusive). Requests arrive in increasing time order, so the oldest are always at the front.

Step 1 / 20
Java
1class RecentCounter {
2 private final Deque<Integer> q = new ArrayDeque<>(); private final int window;
3 RecentCounter(int window) { this.window = window; }
4 int ping(int t) {
5 q.addLast(t);
6 while (q.peekFirst() < t - window) q.pollFirst(); // too old to count
7 return q.size();
8 }
9}

Live state

window

5

No condition evaluated on this step
Execution trace (dry run table)Show 20 steps
StepLineStateWhat happens
1L2window=5Count requests whose time is within the last 5 time units (inclusive). Requests arrive in increasing time order, so the oldest are always at the front.
2L5ping=1 size=1ping(1): add 1 at the rear.
3L7ping=1 return=1The oldest request (1) is inside the window, so ping(1) returns 1.
4L5ping=3 size=2ping(3): add 3 at the rear.
5L7ping=3 return=2The oldest request (1) is inside the window, so ping(3) returns 2.
6L5ping=5 size=3ping(5): add 5 at the rear.
7L7ping=5 return=3The oldest request (1) is inside the window, so ping(5) returns 3.
8L5ping=9 size=4ping(9): add 9 at the rear.
9L6ping=9 oldest=11 is older than 4: it falls out of the window and is removed.
10L6ping=9 oldest=33 is older than 4: it falls out of the window and is removed.
11L7ping=9 return=2The oldest request (5) is inside the window, so ping(9) returns 2.
12L5ping=10 size=3ping(10): add 10 at the rear.
13L7ping=10 return=3The oldest request (5) is inside the window, so ping(10) returns 3.
14L5ping=14 size=4ping(14): add 14 at the rear.
15L6ping=14 oldest=55 is older than 9: it falls out of the window and is removed.
16L7ping=14 return=3The oldest request (9) is inside the window, so ping(14) returns 3.
17L5ping=15 size=4ping(15): add 15 at the rear.
18L6ping=15 oldest=99 is older than 10: it falls out of the window and is removed.
19L7ping=15 return=3The oldest request (10) is inside the window, so ping(15) returns 3.
20L9return=[1, 2, 3, 2, 3, 3, 3]Returned counts: [1, 2, 3, 2, 3, 3, 3]. Every request is added once and removed at most once: O(1) amortised per ping.
13.07.6

Round robin

Round-robin scheduling gives every process a fixed **time quantum** in turn. The ready queue holds the waiting processes; the scheduler dequeues one, runs it for at most one quantum, and if it is not finished puts it at the rear. Every process makes progress, so none starves.

The quantum is the tuning knob. A huge quantum (larger than every burst) degenerates to first-come first-served; a tiny quantum is fair but spends time switching. The animation draws the timeline of who ran when: try “Quantum 1 (fairest)” and “Quantum above every burst (FIFO)”.

Java
1static int[] roundRobin(int[] burst, int quantum) { // returns each process's finish time
2 int n = burst.length, time = 0; int[] left = burst.clone(), finish = new int[n];
3 Queue<Integer> ready = new ArrayDeque<>();
4 for (int i = 0; i < n; i++) ready.offer(i);
5 while (!ready.isEmpty()) {
6 int p = ready.poll();
7 int run = Math.min(quantum, left[p]);
8 time += run; left[p] -= run;
9 if (left[p] > 0) ready.offer(p); // not done: back of the line
10 else finish[p] = time;
11 }
12 return finish;
13}

Round-robin scheduling with a ready queue

Interactive · edit the input

Your input

Try
t=0

P0

empty

P1

empty

P2

empty

P3

empty
01

ready queue · P(left)

P0(5)front
P1(3)
P2(8)
P3(2)rear

finish times

…P0
…P1
…P2
…P3
Setup

All 4 processes wait in the ready queue in arrival order. Each gets at most 3 time units per turn.

Step 1 / 23
Java
1static int[] roundRobin(int[] burst, int quantum) { // returns each process's finish time
2 int n = burst.length, time = 0; int[] left = burst.clone(), finish = new int[n];
3 Queue<Integer> ready = new ArrayDeque<>();
4 for (int i = 0; i < n; i++) ready.offer(i);
5 while (!ready.isEmpty()) {
6 int p = ready.poll();
7 int run = Math.min(quantum, left[p]);
8 time += run; left[p] -= run;
9 if (left[p] > 0) ready.offer(p); // not done: back of the line
10 else finish[p] = time;
11 }
12 return finish;
13}

Live state

time

0

quantum

3

processes

4

No condition evaluated on this step
Execution trace (dry run table)Show 23 steps
StepLineStateWhat happens
1L4time=0 quantum=3 processes=4All 4 processes wait in the ready queue in arrival order. Each gets at most 3 time units per turn.
2L6p=P0 run=3 time=0poll() gives P0 the CPU. It runs min(3, 5) = 3.
3L8time=3 left P0=2The clock is 3; P0 has 2 left.
4L9time=3 p=P0P0 is not finished, so it queues up again at the rear.
5L6p=P1 run=3 time=3poll() gives P1 the CPU. It runs min(3, 3) = 3.
6L8time=6 left P1=0The clock is 6; P1 has 0 left.
7L10time=6 p=P1 finish=6P1 is finished at time 6.
8L6p=P2 run=3 time=6poll() gives P2 the CPU. It runs min(3, 8) = 3.
9L8time=9 left P2=5The clock is 9; P2 has 5 left.
10L9time=9 p=P2P2 is not finished, so it queues up again at the rear.
11L6p=P3 run=2 time=9poll() gives P3 the CPU. It runs min(3, 2) = 2.
12L8time=11 left P3=0The clock is 11; P3 has 0 left.
13L10time=11 p=P3 finish=11P3 is finished at time 11.
14L6p=P0 run=2 time=11poll() gives P0 the CPU. It runs min(3, 2) = 2.
15L8time=13 left P0=0The clock is 13; P0 has 0 left.
16L10time=13 p=P0 finish=13P0 is finished at time 13.
17L6p=P2 run=3 time=13poll() gives P2 the CPU. It runs min(3, 5) = 3.
18L8time=16 left P2=2The clock is 16; P2 has 2 left.
19L9time=16 p=P2P2 is not finished, so it queues up again at the rear.
20L6p=P2 run=2 time=16poll() gives P2 the CPU. It runs min(3, 2) = 2.
21L8time=18 left P2=0The clock is 18; P2 has 0 left.
22L10time=18 p=P2 finish=18P2 is finished at time 18.
23L12time=18 return=[13, 6, 18, 11]Finish times [13, 6, 18, 11]; waiting time (finish minus burst) per process: [8, 3, 10, 9].

Practice ladder

Now try it yourself

Easy

  • Number of Recent Calls
  • Time Needed to Buy Tickets

Medium

  • Rotting Oranges
  • Task Scheduler

Advanced

  • Shortest Path in Binary Matrix
  • Design Hit Counter

Key takeaways

  • BFS uses a FIFO queue for its frontier, which is why it finds shortest paths in unweighted graphs.
  • Priorities need a PriorityQueue; bounded buffers between threads need a blocking ring.
  • A queue of timestamps counts a sliding time window in O(1) amortised.