Chapter 13: Queues, Deques and Circular Buffers
13.01Beginner / Intermediate6 topics12 min2 interactive animations

Queue model

The queue abstract data type: first-in first-out order, enqueue and dequeue, the front and the rear, what underflow and overflow mean, and the invariants every implementation must keep.

Learning goals

  • State the FIFO rule and the queue operations
  • Choose between the exception-throwing and value-returning methods of Java's Queue
  • Explain underflow and overflow for bounded and unbounded queues
  • List the invariants that every queue implementation must preserve

Prerequisites

Stacks and stack-based algorithmsArrays and dynamic arrays

A queue is the line at a ticket counter: whoever arrived first is served first, newcomers join at the back, and nobody may jump ahead.

13.01.1

FIFO

A queue is a collection with two working ends. New values join at the **rear**; values are served from the **front**. The value that has waited longest always leaves first: first in, first out (FIFO). This is the mirror image of a stack, where the newest value leaves first.

FIFO matches problems where fairness or arrival order matters: print jobs, network packets, tasks waiting for a worker, and the frontier of a breadth-first search. Run the animation and compare the output order with the order of the enqueues.

Operation sequenceQueue after (front to rear)Returned
enqueue 1, enqueue 2, enqueue 31, 2, 3-
dequeue2, 31
enqueue 42, 3, 4-
dequeue, dequeue42, then 3

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.01.2

Enqueue

Enqueue adds a value at the rear. It touches only the rear end, so a good implementation does it in O(1) and never moves the values already waiting. In Java the Queue interface offers two methods: offer(x) returns false if the queue cannot accept the value, and add(x) throws IllegalStateException in that case.

For an unbounded queue such as ArrayDeque both methods always succeed, so the difference only matters for bounded queues such as ArrayBlockingQueue.

Java
1static void enqueueAll(Queue<Integer> queue, int[] values) {
2 for (int v : values) {
3 if (!queue.offer(v)) { // bounded queue: reject without throwing
4 System.out.println("rejected " + v);
5 }
6 }
7}
13.01.3

Dequeue

Dequeue removes and returns the front value. Java again gives a pair of methods: poll() returns null when the queue is empty, remove() throws NoSuchElementException. Because ArrayDeque does not allow null elements, a null from poll() can only mean empty.

Prefer poll() in loops that drain a queue and remove() when an empty queue would be a bug you want to see immediately.

PurposeThrows an exceptionReturns a special value
Insert at the rearadd(x)offer(x): false when full
Remove the frontremove()poll(): null when empty
Examine the frontelement()peek(): null when empty
13.01.4

Front and rear

The **front** (also called the head) is the value that will leave next; the **rear** (the tail) is the value that joined last. peek() reads the front without removing it. The Queue interface has no method to read the rear: use a Deque (peekLast()) or keep your own reference when you need it.

An implementation therefore tracks two positions. An array queue keeps two indices, a linked queue keeps two node references. The next lessons show both.

  • front = oldest value = next to be dequeued
  • rear = newest value = last one enqueued
  • With one value, front and rear are the same element
  • peek() looks at the front only; it never changes the queue
13.01.5

Underflow/overflow

**Underflow** is dequeuing or peeking at an empty queue. **Overflow** is enqueuing into a queue that is full. Only bounded queues can overflow; an ArrayDeque or LinkedList simply grows.

ArrayBlockingQueue is a bounded queue: offer returns false when it is full and add throws IllegalStateException with the message Queue full. Choose the Bounded option in the animation, give it a capacity of 2 and enqueue three values to watch the overflow; the “Underflow” preset shows poll and peek on an empty queue.

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.01.6

Queue invariants

An invariant is a fact that must hold after every operation. If any of these breaks, the structure is corrupt, and checking them is the fastest way to debug a hand-written queue.

InvariantWhy it matters
size = enqueues - successful dequeuesNo value is lost or duplicated
Values leave in the order they arrivedThis is the definition of FIFO
The queue is empty exactly when size == 0Underflow must be detectable
Bounded queue: size never exceeds capacityOverflow must be detectable
Enqueue changes only the rear, dequeue only the frontBoth are O(1) without shifting

Practice ladder

Now try it yourself

Easy

  • Show the queue after enqueue 1, 2, 3, dequeue, enqueue 4
  • What does ArrayDeque.poll() return on an empty queue?

Medium

  • Why can ArrayDeque not store null while LinkedList can?
  • When would you prefer offer over add?

Advanced

  • Design a bounded queue API where a full queue blocks the producer instead of rejecting

Key takeaways

  • A queue is FIFO: enqueue at the rear, dequeue at the front, both O(1).
  • Queue offers exception-throwing (add, remove, element) and value-returning (offer, poll, peek) methods.
  • Underflow and overflow must be handled; only bounded queues can overflow.