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
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.
1static int[][] bfsDistances(int[][] g, int sr, int sc) { // 0 = wall, 1 = open2int R = g.length, C = g[0].length;3int[][] dist = new int[R][C]; for (int[] row : dist) Arrays.fill(row, -1);4Deque<int[]> frontier = new ArrayDeque<>();5dist[sr][sc] = 0; frontier.offer(new int[]{sr, sc});6int[][] dirs = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};7while (!frontier.isEmpty()) {8int[] cur = frontier.poll(); // the oldest discovered cell9for (int[] d : dirs) {10int r = cur[0] + d[0], c = cur[1] + d[1];11if (r < 0 || r >= R || c < 0 || c >= C || g[r][c] == 0 || dist[r][c] != -1) continue;12dist[r][c] = dist[cur[0]][cur[1]] + 1;13frontier.offer(new int[]{r, c});14}15}16return dist;17}
BFS on a grid: shortest path and multi-source distances
Interactive · edit the inputgrid · 3 × 5
queue (front first)
Start at the top-left cell with distance 0.
1static int shortestPath(int[][] g) { // 4-directional moves, 0 = wall2int R = g.length, C = g[0].length;3int[][] dist = new int[R][C]; for (int[] row : dist) Arrays.fill(row, -1);4Deque<int[]> queue = new ArrayDeque<>();5// single source: (0, 0); multi-source: every cell equal to 2, all at distance 06dist[0][0] = 0; queue.offer(new int[]{0, 0});7while (!queue.isEmpty()) {8int[] cur = queue.poll();9for (int[] d : new int[][]{{-1, 0}, {0, 1}, {1, 0}, {0, -1}}) {10int r = cur[0] + d[0], c = cur[1] + d[1];11if (r < 0 || r >= R || c < 0 || c >= C || g[r][c] == 0 || dist[r][c] != -1) continue;12dist[r][c] = dist[cur[0]][cur[1]] + 1; // first arrival is the shortest, because BFS goes level by level13queue.offer(new int[]{r, c});14}15}16return dist[R - 1][C - 1]; // -1 when unreachable; multi-source: max over all cells17}
Live state
sources
1
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L5 | sources=1 | Start at the top-left cell with distance 0. |
| 2 | L12 | cell=(0,0) dist=0 | Expand (0,0) at distance 0: reached (0,1)=1. |
| 3 | L12 | cell=(0,1) dist=1 | Expand (0,1) at distance 1: reached (1,1)=2. |
| 4 | L12 | cell=(1,1) dist=2 | Expand (1,1) at distance 2: reached (1,2)=3, (2,1)=3. |
| 5 | L12 | cell=(1,2) dist=3 | Expand (1,2) at distance 3: reached (1,3)=4, (2,2)=4. |
| 6 | L12 | cell=(2,1) dist=3 | Expand (2,1): every open neighbour is already reached or blocked. |
| 7 | L12 | cell=(1,3) dist=4 | Expand (1,3) at distance 4: reached (0,3)=5, (1,4)=5, (2,3)=5. |
| 8 | L12 | cell=(2,2) dist=4 | Expand (2,2): every open neighbour is already reached or blocked. |
| 9 | L12 | cell=(0,3) dist=5 | Expand (0,3) at distance 5: reached (0,4)=6. |
| 10 | L12 | cell=(1,4) dist=5 | Expand (1,4) at distance 5: reached (2,4)=6. |
| 11 | L12 | cell=(2,3) dist=5 | Expand (2,3): every open neighbour is already reached or blocked. |
| 12 | L12 | cell=(0,4) dist=6 | Expand (0,4): every open neighbour is already reached or blocked. |
| 13 | L12 | cell=(2,4) dist=6 | Expand (2,4): every open neighbour is already reached or blocked. |
| 14 | L16 | return=6 | The bottom-right cell is 6 steps away, the minimum, because BFS reaches cells in order of distance. |
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.
1record Task(String name, int priority, long arrival) {}23static List<String> schedule(List<Task> tasks) {4PriorityQueue<Task> pq = new PriorityQueue<>(5Comparator.comparingInt(Task::priority).thenComparingLong(Task::arrival)); // lower number = more urgent6pq.addAll(tasks);7List<String> order = new ArrayList<>();8while (!pq.isEmpty()) order.add(pq.poll().name());9return order;10}
PriorityQueue: offer, poll and peek on a binary heap
Interactive · edit the inputThe heap is empty.
int[] arr · length 0
polled so far (in order)
An empty min-queue. It is a binary heap stored in an array: the children of index i are 2i+1 and 2i+2.
1Comparator<Integer> cmp = Comparator.naturalOrder(); // Comparator.reverseOrder() makes a max-queue2PriorityQueue<Integer> pq = new PriorityQueue<>(cmp);3pq.offer(x); // append at the end, then sift up4Integer top = pq.poll(); // last element to the root, then sift down5Integer next = pq.peek(); // the root only; nothing is removed
Live state
size
0
order
min first
Execution trace (dry run table)Show 26 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | size=0 order=min first | An empty min-queue. It is a binary heap stored in an array: the children of index i are 2i+1 and 2i+2. |
| 2 | L3 | op=offer(5) size=1 | offer(5): append at the end (index 0), the only place that keeps the tree complete. |
| 3 | L3 | op=offer(3) size=2 | offer(3): append at the end (index 1), the only place that keeps the tree complete. |
| 4 | L3 | op=offer(3) child=3 parent=5 | The child has higher priority than its parent, so they swap. |
| 5 | L3 | op=offer(3) | Swapped: 3 moves up to index 0. |
| 6 | L3 | op=offer(8) size=3 | offer(8): append at the end (index 2), the only place that keeps the tree complete. |
| 7 | L3 | op=offer(8) child=8 parent=3 | The parent keeps priority over the child (ties do not swap): sift up stops. |
| 8 | L3 | op=offer(1) size=4 | offer(1): append at the end (index 3), the only place that keeps the tree complete. |
| 9 | L3 | op=offer(1) child=1 parent=5 | The child has higher priority than its parent, so they swap. |
| 10 | L3 | op=offer(1) | Swapped: 1 moves up to index 1. |
| 11 | L3 | op=offer(1) child=1 parent=3 | The child has higher priority than its parent, so they swap. |
| 12 | L3 | op=offer(1) | Swapped: 1 moves up to index 0. |
| 13 | L4 | op=poll() returned=1 size=3 | poll() returns the root 1. The last element (5) moves to the root to fill the gap, then sifts down. |
| 14 | L4 | op=poll() node=5 better child=3 | The better child (3) beats the node, so they swap. |
| 15 | L4 | op=poll() | Swapped: 3 moves up to index 0. |
| 16 | L3 | op=offer(4) size=4 | offer(4): append at the end (index 3), the only place that keeps the tree complete. |
| 17 | L3 | op=offer(4) child=4 parent=5 | The child has higher priority than its parent, so they swap. |
| 18 | L3 | op=offer(4) | Swapped: 4 moves up to index 1. |
| 19 | L3 | op=offer(4) child=4 parent=3 | The parent keeps priority over the child (ties do not swap): sift up stops. |
| 20 | L4 | op=poll() returned=3 size=3 | poll() returns the root 3. The last element (5) moves to the root to fill the gap, then sifts down. |
| 21 | L4 | op=poll() node=5 better child=4 | The better child (4) beats the node, so they swap. |
| 22 | L4 | op=poll() | Swapped: 4 moves up to index 0. |
| 23 | L4 | op=poll() returned=4 size=2 | poll() returns the root 4. The last element (8) moves to the root to fill the gap, then sifts down. |
| 24 | L4 | op=poll() node=8 better child=5 | The better child (5) beats the node, so they swap. |
| 25 | L4 | op=poll() | Swapped: 5 moves up to index 0. |
| 26 | L5 | size=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. |
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.
1static void pipeline() throws InterruptedException {2BlockingQueue<Integer> buffer = new ArrayBlockingQueue<>(4); // bounded: capacity 43Thread producer = new Thread(() -> {4try { for (int i = 1; i <= 8; i++) buffer.put(i); buffer.put(-1); } // put blocks while full5catch (InterruptedException e) { Thread.currentThread().interrupt(); }6});7Thread consumer = new Thread(() -> {8try { for (int v; (v = buffer.take()) != -1; ) System.out.println("consumed " + v); } // take blocks while empty9catch (InterruptedException e) { Thread.currentThread().interrupt(); }10});11producer.start(); consumer.start();12producer.join(); consumer.join();13}
Circular queue: head, tail and a size counter
Interactive · edit the inputdata[4] · head 0 · tail 0 · size 0
An array of 4. head is where the next value leaves, tail is where the next value arrives, size counts the values.
1class RingQueue {2private final int[] data; private int head, tail, size;3RingQueue(int capacity) { data = new int[capacity]; }4boolean offer(int x) {5if (size == data.length) return false; // full6data[tail] = x;7tail = (tail + 1) % data.length; // wrap around8size++; return true;9}10Integer poll() {11if (size == 0) return null; // empty12int x = data[head];13head = (head + 1) % data.length; // wrap around14size--; return x;15}16}
Live state
head
0
tail
0
size
0
Execution trace (dry run table)Show 29 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | head=0 tail=0 size=0 | An array of 4. head is where the next value leaves, tail is where the next value arrives, size counts the values. |
| 2 | L5 | head=0 tail=0 size=0 op=offer(1) | offer(1): there is room. |
| 3 | L6 | head=0 tail=0 size=1 op=offer(1) | data[0] = 1. |
| 4 | L7 | head=0 tail=1 size=1 op=offer(1) | tail = (0 + 1) % 4 = 1. |
| 5 | L5 | head=0 tail=1 size=1 op=offer(2) | offer(2): there is room. |
| 6 | L6 | head=0 tail=1 size=2 op=offer(2) | data[1] = 2. |
| 7 | L7 | head=0 tail=2 size=2 op=offer(2) | tail = (1 + 1) % 4 = 2. |
| 8 | L5 | head=0 tail=2 size=2 op=offer(3) | offer(3): there is room. |
| 9 | L6 | head=0 tail=2 size=3 op=offer(3) | data[2] = 3. |
| 10 | L7 | head=0 tail=3 size=3 op=offer(3) | tail = (2 + 1) % 4 = 3. |
| 11 | L11 | head=0 tail=3 size=3 op=poll() | poll(): there is a value at head. |
| 12 | L12 | head=0 tail=3 size=3 op=poll() returned=1 | x = data[0] = 1. The slot keeps its old number (shown struck out when free): only head and size decide what is live. |
| 13 | L13 | head=1 tail=3 size=2 op=poll() returned=1 | head = (0 + 1) % 4 = 1. Returned 1. |
| 14 | L11 | head=1 tail=3 size=2 op=poll() | poll(): there is a value at head. |
| 15 | L12 | head=1 tail=3 size=2 op=poll() returned=2 | x = data[1] = 2. The slot keeps its old number (shown struck out when free): only head and size decide what is live. |
| 16 | L13 | head=2 tail=3 size=1 op=poll() returned=2 | head = (1 + 1) % 4 = 2. Returned 2. |
| 17 | L5 | head=2 tail=3 size=1 op=offer(4) | offer(4): there is room. |
| 18 | L6 | head=2 tail=3 size=2 op=offer(4) | data[3] = 4. |
| 19 | L7 | head=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. |
| 20 | L5 | head=2 tail=0 size=2 op=offer(5) | offer(5): there is room. |
| 21 | L6 | head=2 tail=0 size=3 op=offer(5) | data[0] = 5. |
| 22 | L7 | head=2 tail=1 size=3 op=offer(5) | tail = (0 + 1) % 4 = 1. |
| 23 | L5 | head=2 tail=1 size=3 op=offer(6) | offer(6): there is room. |
| 24 | L6 | head=2 tail=1 size=4 op=offer(6) | data[1] = 6. |
| 25 | L7 | head=2 tail=2 size=4 op=offer(6) | tail = (1 + 1) % 4 = 2. |
| 26 | L11 | head=2 tail=2 size=4 op=poll() | poll(): there is a value at head. |
| 27 | L12 | head=2 tail=2 size=4 op=poll() returned=3 | x = data[2] = 3. The slot keeps its old number (shown struck out when free): only head and size decide what is live. |
| 28 | L13 | head=3 tail=2 size=3 op=poll() returned=3 | head = (2 + 1) % 4 = 3. Returned 3. |
| 29 | L16 | head=3 tail=2 size=3 return=[4, 5, 6] | Queue contents from head: [4, 5, 6]; 0 operations rejected. |
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).
1class PrintSpooler {2private final Queue<String> jobs = new ArrayDeque<>();3void submit(String doc) { jobs.offer(doc); }4String printNext() {5String doc = jobs.poll(); // null when nothing is waiting6return doc == null ? "idle" : "printing " + doc;7}8}
Queue: enqueue, dequeue, peek, overflow and underflow
Interactive · edit the inputqueue · front → rear · size 0 of 3
An empty queue. Values join at the rear and leave from the front, so the oldest value is always served first (FIFO).
1class BoundedQueue {2private final ArrayDeque<Integer> q = new ArrayDeque<>(); private final int capacity;3boolean offer(int x) {4if (q.size() == capacity) return false; // overflow: reject5q.addLast(x); return true; // join at the rear6}7Integer poll() {8return q.pollFirst(); // front leaves first; null when empty9}10Integer peek() { return q.peekFirst(); } // front, not removed11}
Live state
size
0
capacity
3
Execution trace (dry run table)Show 11 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | size=0 capacity=3 | An empty queue. Values join at the rear and leave from the front, so the oldest value is always served first (FIFO). |
| 2 | L5 | op=offer(5) size=1 rear=5 | addLast(5): 5 joins behind everything already waiting. Nothing else moves. |
| 3 | L5 | op=offer(8) size=2 rear=8 | addLast(8): 8 joins behind everything already waiting. Nothing else moves. |
| 4 | L5 | op=offer(3) size=3 rear=3 | addLast(3): 3 joins behind everything already waiting. Nothing else moves. |
| 5 | L10 | op=peek() front=5 size=3 | peek() returns 5 without removing it. |
| 6 | L8 | op=poll() returned=5 size=2 | pollFirst() returns 5, the value that has waited longest. The new front is 8. |
| 7 | L5 | op=offer(7) size=3 rear=7 | addLast(7): 7 joins behind everything already waiting. Nothing else moves. |
| 8 | L8 | op=poll() returned=8 size=2 | pollFirst() returns 8, the value that has waited longest. The new front is 3. |
| 9 | L8 | op=poll() returned=3 size=1 | pollFirst() returns 3, the value that has waited longest. The new front is 7. |
| 10 | L8 | op=poll() returned=7 size=0 | pollFirst() returns 7, the value that has waited longest. The queue is empty again. |
| 11 | L11 | size=0 failed=0 return=[] | Final queue (front to rear): []; 0 operations failed. |
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.
1class RecentCounter {2private final Deque<Integer> q = new ArrayDeque<>(); private final int window;3RecentCounter(int window) { this.window = window; }4int ping(int t) {5q.addLast(t);6while (q.peekFirst() < t - window) q.pollFirst(); // too old to count7return 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 inputqueue of requests in [t - 5, t]
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.
1class RecentCounter {2private final Deque<Integer> q = new ArrayDeque<>(); private final int window;3RecentCounter(int window) { this.window = window; }4int ping(int t) {5q.addLast(t);6while (q.peekFirst() < t - window) q.pollFirst(); // too old to count7return q.size();8}9}
Live state
window
5
Execution trace (dry run table)Show 20 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | window=5 | 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. |
| 2 | L5 | ping=1 size=1 | ping(1): add 1 at the rear. |
| 3 | L7 | ping=1 return=1 | The oldest request (1) is inside the window, so ping(1) returns 1. |
| 4 | L5 | ping=3 size=2 | ping(3): add 3 at the rear. |
| 5 | L7 | ping=3 return=2 | The oldest request (1) is inside the window, so ping(3) returns 2. |
| 6 | L5 | ping=5 size=3 | ping(5): add 5 at the rear. |
| 7 | L7 | ping=5 return=3 | The oldest request (1) is inside the window, so ping(5) returns 3. |
| 8 | L5 | ping=9 size=4 | ping(9): add 9 at the rear. |
| 9 | L6 | ping=9 oldest=1 | 1 is older than 4: it falls out of the window and is removed. |
| 10 | L6 | ping=9 oldest=3 | 3 is older than 4: it falls out of the window and is removed. |
| 11 | L7 | ping=9 return=2 | The oldest request (5) is inside the window, so ping(9) returns 2. |
| 12 | L5 | ping=10 size=3 | ping(10): add 10 at the rear. |
| 13 | L7 | ping=10 return=3 | The oldest request (5) is inside the window, so ping(10) returns 3. |
| 14 | L5 | ping=14 size=4 | ping(14): add 14 at the rear. |
| 15 | L6 | ping=14 oldest=5 | 5 is older than 9: it falls out of the window and is removed. |
| 16 | L7 | ping=14 return=3 | The oldest request (9) is inside the window, so ping(14) returns 3. |
| 17 | L5 | ping=15 size=4 | ping(15): add 15 at the rear. |
| 18 | L6 | ping=15 oldest=9 | 9 is older than 10: it falls out of the window and is removed. |
| 19 | L7 | ping=15 return=3 | The oldest request (10) is inside the window, so ping(15) returns 3. |
| 20 | L9 | return=[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. |
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)”.
1static int[] roundRobin(int[] burst, int quantum) { // returns each process's finish time2int n = burst.length, time = 0; int[] left = burst.clone(), finish = new int[n];3Queue<Integer> ready = new ArrayDeque<>();4for (int i = 0; i < n; i++) ready.offer(i);5while (!ready.isEmpty()) {6int p = ready.poll();7int run = Math.min(quantum, left[p]);8time += run; left[p] -= run;9if (left[p] > 0) ready.offer(p); // not done: back of the line10else finish[p] = time;11}12return finish;13}
Round-robin scheduling with a ready queue
Interactive · edit the inputP0
P1
P2
P3
ready queue · P(left)
finish times
All 4 processes wait in the ready queue in arrival order. Each gets at most 3 time units per turn.
1static int[] roundRobin(int[] burst, int quantum) { // returns each process's finish time2int n = burst.length, time = 0; int[] left = burst.clone(), finish = new int[n];3Queue<Integer> ready = new ArrayDeque<>();4for (int i = 0; i < n; i++) ready.offer(i);5while (!ready.isEmpty()) {6int p = ready.poll();7int run = Math.min(quantum, left[p]);8time += run; left[p] -= run;9if (left[p] > 0) ready.offer(p); // not done: back of the line10else finish[p] = time;11}12return finish;13}
Live state
time
0
quantum
3
processes
4
Execution trace (dry run table)Show 23 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | time=0 quantum=3 processes=4 | All 4 processes wait in the ready queue in arrival order. Each gets at most 3 time units per turn. |
| 2 | L6 | p=P0 run=3 time=0 | poll() gives P0 the CPU. It runs min(3, 5) = 3. |
| 3 | L8 | time=3 left P0=2 | The clock is 3; P0 has 2 left. |
| 4 | L9 | time=3 p=P0 | P0 is not finished, so it queues up again at the rear. |
| 5 | L6 | p=P1 run=3 time=3 | poll() gives P1 the CPU. It runs min(3, 3) = 3. |
| 6 | L8 | time=6 left P1=0 | The clock is 6; P1 has 0 left. |
| 7 | L10 | time=6 p=P1 finish=6 | P1 is finished at time 6. |
| 8 | L6 | p=P2 run=3 time=6 | poll() gives P2 the CPU. It runs min(3, 8) = 3. |
| 9 | L8 | time=9 left P2=5 | The clock is 9; P2 has 5 left. |
| 10 | L9 | time=9 p=P2 | P2 is not finished, so it queues up again at the rear. |
| 11 | L6 | p=P3 run=2 time=9 | poll() gives P3 the CPU. It runs min(3, 2) = 2. |
| 12 | L8 | time=11 left P3=0 | The clock is 11; P3 has 0 left. |
| 13 | L10 | time=11 p=P3 finish=11 | P3 is finished at time 11. |
| 14 | L6 | p=P0 run=2 time=11 | poll() gives P0 the CPU. It runs min(3, 2) = 2. |
| 15 | L8 | time=13 left P0=0 | The clock is 13; P0 has 0 left. |
| 16 | L10 | time=13 p=P0 finish=13 | P0 is finished at time 13. |
| 17 | L6 | p=P2 run=3 time=13 | poll() gives P2 the CPU. It runs min(3, 5) = 3. |
| 18 | L8 | time=16 left P2=2 | The clock is 16; P2 has 2 left. |
| 19 | L9 | time=16 p=P2 | P2 is not finished, so it queues up again at the rear. |
| 20 | L6 | p=P2 run=2 time=16 | poll() gives P2 the CPU. It runs min(3, 2) = 2. |
| 21 | L8 | time=18 left P2=0 | The clock is 18; P2 has 0 left. |
| 22 | L10 | time=18 p=P2 finish=18 | P2 is finished at time 18. |
| 23 | L12 | time=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.