Queues

First In, First Out (FIFO)

FRONT REAR | | v v +-----+-----+-----+-----+-----+-----+-----+ dequeue <-- | A | B | C | D | E | F | G | <-- enqueue +-----+-----+-----+-----+-----+-----+-----+ First in Last in First out Last out

CS205 Data Structures

Arrow keys or Space to navigate

1 / 18

What is a Queue?

A queue is a collection of elements with a First-In, First-Out (FIFO) access policy.

  • Elements are added at the rear (back)
  • Elements are removed from the front
  • No access to elements in the middle

Analogy: Coffee Shop Line

The first person who lines up is the first person served. New arrivals join the back of the line. Cutting is not allowed!

Coffee Shop Queue: COUNTER DOOR | | v v +------+------+------+------+------+ | Alex | Beth | Carl | Dana | Evan | +------+------+------+------+------+ ^ ^ | | FRONT REAR (served next) (just arrived)

Key Idea

FIFO = the element that has been waiting the longest gets served first. Fair and orderly!

2 / 18

The Queue ADT

Core Operations

OperationDescriptionTime
enqueue(e)Insert element at rearO(1)
dequeue()Remove & return front elementO(1)
front()Return front without removingO(1)
isEmpty()Is the queue empty?O(1)
size()Number of elementsO(1)

Key Idea

All core queue operations are O(1) -- constant time, regardless of how many elements are stored.

Java Interface

public interface Queue<E> { /** Insert element at the rear. */ void enqueue(E element); /** Remove and return front element. * Throws exception if empty. */ E dequeue(); /** Return front element without * removing it. */ E front(); /** Return number of elements. */ int size(); /** Is the queue empty? */ boolean isEmpty(); }
3 / 18

Enqueue Operation

Add a new element to the rear of the queue.

Before: enqueue("Z")

front rear | | v v +-----+-----+-----+-----+ | A | B | C | D | +-----+-----+-----+-----+ size = 4

After:

front rear | | v v +-----+-----+-----+-----+-----+ | A | B | C | D | Z | +-----+-----+-----+-----+-----+ size = 5

Code (Array-Based)

public void enqueue(E element) { if (size == data.length) throw new IllegalStateException( "Queue is full"); // Add at rear position data[rear] = element; // Advance rear (with wrap-around) rear = (rear + 1) % data.length; size++; }

Key Idea

Enqueue always operates on the rear end. The front is untouched. This is O(1).

4 / 18

Dequeue Operation

Remove and return the element at the front of the queue.

Before: dequeue() -> returns "A"

front rear | | v v +-----+-----+-----+-----+-----+ | A | B | C | D | Z | +-----+-----+-----+-----+-----+

After:

front rear | | v v +-----+-----+-----+-----+-----+ | | B | C | D | Z | +-----+-----+-----+-----+-----+ returned: "A" size = 4

Code (Array-Based)

public E dequeue() { if (isEmpty()) throw new NoSuchElementException( "Queue is empty"); E result = data[front]; data[front] = null; // help GC // Advance front (with wrap-around) front = (front + 1) % data.length; size--; return result; }

Warning: Always Check isEmpty!

Calling dequeue() on an empty queue is an error. Always guard with isEmpty() or handle the exception.

5 / 18

Stack vs Queue

Stack -- LIFO

Last In, First Out

push(A), push(B), push(C): +-----+ | C | <-- top (pop here) +-----+ | B | +-----+ | A | +-----+ pop() -> C (last in, first out)

Think: stack of plates. You take the top plate off first.

OperationStack
Insertpush(e)
Removepop()
Peektop()

Queue -- FIFO

First In, First Out

enqueue(A), enqueue(B), enqueue(C): front rear | | v v +-----+-----+-----+ | A | B | C | +-----+-----+-----+ dequeue() -> A (first in, first out)

Think: line at a store. The first person in line is served first.

OperationQueue
Insertenqueue(e)
Removedequeue()
Peekfront()

Key Idea

Stack and Queue are both restricted-access data structures. The difference is which end you remove from. Stack: same end as insert. Queue: opposite end from insert.

6 / 18

Array-Based Queue (Naive Approach)

Store elements in an array. Enqueue at the end, dequeue from index 0.

The Problem: Dequeue Shifts O(n)

Before dequeue(): index: 0 1 2 3 4 +-----+-----+-----+-----+-----+ | A | B | C | D | E | +-----+-----+-----+-----+-----+ After dequeue() -- must shift everything left: index: 0 1 2 3 4 +-----+-----+-----+-----+-----+ | B | C | D | E | | +-----+-----+-----+-----+-----+ ^<< ^<< ^<< ^<< shift shift shift shift

Warning: O(n) Dequeue!

Every dequeue() shifts all remaining elements one position to the left. For n elements, that is O(n) work per dequeue.

Alternative: Move Front Pointer

Just advance the front index: index: 0 1 2 3 4 +-----+-----+-----+-----+-----+ | | B | C | D | E | +-----+-----+-----+-----+-----+ ^ ^ front rear O(1) dequeue! But... wasted space grows forever. Array runs out.

This is better, but we waste all the space before front. Solution: wrap around!

7 / 18

Circular Array Implementation

When the rear reaches the end of the array, it wraps around to index 0 using modulo arithmetic.

The Circular Idea

Linear view (array of size 8): index: 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | | | C | D | E | | | | +---+---+---+---+---+---+---+---+ ^ ^ front rear After more enqueues fill to the end: index: 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | | | C | D | E | F | G | H | +---+---+---+---+---+---+---+---+ ^ ^ front rear enqueue("I") -- rear wraps to index 0! index: 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | I | | C | D | E | F | G | H | +---+---+---+---+---+---+---+---+ ^ ^ rear front

The Magic Formula

Advance front: front = (front + 1) % N Advance rear: rear = (rear + 1) % N where N = array capacity

Key Idea: Modulo Wraps Around

If N = 8 and rear = 7:
(7 + 1) % 8 = 0
The index wraps back to the beginning! The array is treated as a ring.

Visualize as a Ring

7 0 1 +---+---+---+ 6 | H | I | | 2 +---+---+---+ 5 | G | | C | 3 +---+---+---+ F E D +---+---+---+ 4 front=2 rear=1 size=7
8 / 18

Circular Array: Step-by-Step Trace

Trace operations on a circular array of capacity N = 5. Indices 0..4.

Operation Array State f r size ───────────────── ────────────────────────────────── ── ── ──── (initial) [ , , , , ] 0 0 0 enqueue(A) [ A, , , , ] 0 1 1 enqueue(B) [ A, B, , , ] 0 2 2 enqueue(C) [ A, B, C, , ] 0 3 3 dequeue() -> A [ , B, C, , ] 1 3 2 dequeue() -> B [ , , C, , ] 2 3 1 enqueue(D) [ , , C, D, ] 2 4 2 enqueue(E) [ , , C, D, E] 2 0 3 ^--- rear wraps: (4+1)%5 = 0 enqueue(F) [ F, , C, D, E] 2 1 4 ^--- rear is now at index 1 dequeue() -> C [ F, , , D, E] 3 1 3 dequeue() -> D [ F, , , , E] 4 1 2 dequeue() -> E [ F, , , , ] 0 1 1 ^--- front wraps: (4+1)%5 = 0

Key Idea

Both front and rear wrap around independently. The array is reused -- no wasted space, no shifting. Every enqueue and dequeue is O(1).

9 / 18

Circular Array: Full vs Empty

A tricky problem: when front == rear, is the queue empty or full?

The Ambiguity

EMPTY queue (size = 0): index: 0 1 2 3 4 +---+---+---+---+---+ | | | | | | +---+---+---+---+---+ ^ front = rear = 0 FULL queue (size = 5): index: 0 1 2 3 4 +---+---+---+---+---+ | A | B | C | D | E | +---+---+---+---+---+ ^ front = rear = 0 ??? Both look the same! front == rear

Solution 1: Use a Count Variable

Maintain a `size` field separately. isEmpty(): return size == 0 isFull(): return size == N (This is the cleanest approach)

Solution 2: Waste One Slot

Never let all N slots be used. Maximum capacity = N - 1. FULL when: (rear + 1) % N == front index: 0 1 2 3 4 +---+---+---+---+---+ | A | B | C | D | | +---+---+---+---+---+ ^ ^ front rear (rear+1)%5 == 0 == front -> FULL One slot is always empty.

Warning

If you use neither solution, you cannot distinguish full from empty. This is a classic bug in circular buffer implementations.

10 / 18

Linked-List-Based Queue

Use a singly linked list with both head and tail pointers.

head tail | | v v +------+---+ +------+---+ +------+---+ +------+------+ | A | --+--->| B | --+--->| C | --+--->| D | null | +------+---+ +------+---+ +------+---+ +------+------+ ^ ^ | | FRONT (dequeue here) REAR (enqueue here)

Enqueue (add at tail) -- O(1)

public void enqueue(E element) { Node<E> newest = new Node<>(element); if (isEmpty()) { head = newest; } else { tail.next = newest; } tail = newest; size++; }

Dequeue (remove from head) -- O(1)

public E dequeue() { if (isEmpty()) throw new NoSuchElementException(); E result = head.data; head = head.next; size--; if (isEmpty()) tail = null; // queue is now empty return result; }

Key Idea: Why Head and Tail?

We need head for O(1) dequeue (remove first node) and tail for O(1) enqueue (append at end). Without tail, enqueue would require traversing the entire list -- O(n)!

11 / 18

Deque (Double-Ended Queue)

A generalization that allows insertion and removal at both ends.

DEQUE (pronounced "deck", not "dee-queue") addFirst / removeFirst addLast / removeLast | | v v <--------+ +--------> | | +-----+-----+-----+-----+-----+-----+ | A | B | C | D | E | F | +-----+-----+-----+-----+-----+-----+ ^ ^ FRONT REAR ^ ^ <--------+ +--------> | | removeFirst / addFirst removeLast / addLast

Deque Operations

OperationDescription
addFirst(e)Insert at front
addLast(e)Insert at rear
removeFirst()Remove from front
removeLast()Remove from rear
first()Peek at front
last()Peek at rear

Key Idea

A Deque subsumes both Stack and Queue:

  • Stack = use only addFirst + removeFirst (or addLast + removeLast)
  • Queue = use addLast + removeFirst

Can be implemented with a doubly linked list or a circular array. Java provides ArrayDeque and LinkedList (both implement Deque).

12 / 18

Application: Print Job Queue

A shared printer uses a queue to manage incoming print jobs fairly.

Print Job Queue: Users submit jobs: Alice -> "report.pdf" (9:00 AM) Bob -> "slides.pptx" (9:01 AM) Carol -> "photo.png" (9:02 AM) Dave -> "essay.docx" (9:03 AM) Queue state: FRONT REAR | | v v +----------+----------+----------+----------+ | report | slides | photo | essay | | (Alice) | (Bob) | (Carol) | (Dave) | +----------+----------+----------+----------+ Printer processes: 1. report.pdf (Alice) -- PRINTING 2. slides.pptx (Bob) -- waiting 3. photo.png (Carol) -- waiting 4. essay.docx (Dave) -- waiting

Analogy: Fair Ordering

Just like a line at a deli counter: first come, first served. The printer does not skip anyone -- jobs are processed in the exact order they were submitted.

Why a Queue?

  • Fairness -- no starvation, every job eventually prints
  • Simplicity -- just enqueue on submit, dequeue when printer is ready
  • Predictability -- users know their position in line

Key Idea

Any system that needs fair, ordered processing of requests is a natural fit for a queue: OS task scheduling, network packet buffers, message queues, etc.

13 / 18

Application: BFS (Breadth-First Search)

A queue drives level-by-level traversal of a tree or graph.

Tree to Traverse

(1) A / | \ / | \ (2)B (3)C (4)D / \ | (5)E (6)F (7)G BFS visits by level: Level 0: A Level 1: B, C, D Level 2: E, F, G Visit order: A B C D E F G

Key Idea

BFS explores all neighbors at the current depth before moving deeper. The queue ensures this level-by-level ordering.

BFS Trace Using Queue

Step Action Queue (front...rear) Visited ──── ────── ──────────────────── ─────── 1 start at A [A] {} 2 dequeue A [] {A} enqueue B,C,D [B, C, D] 3 dequeue B [C, D] {A,B} enqueue E,F [C, D, E, F] 4 dequeue C [D, E, F] {A,B,C} (no children) 5 dequeue D [E, F] {A,B,C,D} enqueue G [E, F, G] 6 dequeue E [F, G] {A,B,C,D,E} 7 dequeue F [G] {A,B,C,D,E,F} 8 dequeue G [] {A,B,C,D,E,F,G} Queue empty -> DONE!
BFS pseudocode: ─────────────────────────────── queue.enqueue(start) while not queue.isEmpty(): node = queue.dequeue() visit(node) for each child of node: queue.enqueue(child)
14 / 18

Application: Hot Potato / Josephus Problem

Players stand in a circle. A "hot potato" is passed k times, and the person holding it is eliminated. Last one standing wins.

Algorithm Using a Queue

hotPotato(names, k): queue = new Queue() for each name in names: queue.enqueue(name) while queue.size() > 1: // Pass potato k times for i = 1 to k: queue.enqueue( queue.dequeue() ) // Eliminate holder eliminated = queue.dequeue() print(eliminated + " is out!") return queue.dequeue() // winner!

Key Idea

Dequeue + re-enqueue simulates circular passing. After k passes, dequeue without re-enqueue = elimination.

Trace: Players = [A,B,C,D,E], k = 3

Queue Pass / Eliminate ─────────────── ────────────────── [A, B, C, D, E] Start [B, C, D, E, A] pass 1: A to back [C, D, E, A, B] pass 2: B to back [D, E, A, B, C] pass 3: C to back [E, A, B, C] D eliminated! [A, B, C, E] pass 1: E to back [B, C, E, A] pass 2: A to back [C, E, A, B] pass 3: B to back [E, A, B] C eliminated! [A, B, E] pass 1: E to back [B, E, A] pass 2: A to back [E, A, B] pass 3: B to back [A, B] E eliminated! [B, A] pass 1: A to back [A, B] pass 2: B to back [B, A] pass 3: A to back [A] B eliminated! Winner: A
15 / 18

Priority Queue Preview

What if not everyone should wait equally? What if some elements are more urgent?

Regular Queue (FIFO)

Arrival order determines service: FRONT REAR | | v v +------+------+------+------+------+ | low | high | med | high | low | +------+------+------+------+------+ dequeue() -> "low" (first in, regardless of priority)

Priority Queue

Priority determines service: +------+------+------+------+------+ | high | high | med | low | low | +------+------+------+------+------+ ^ | removeMin() -> highest priority first (regardless of arrival order!)

Key Idea

A Priority Queue is NOT a queue. It does not follow FIFO. Instead, the element with the highest priority (often the minimum key) is removed first.

Real-World Examples

  • Emergency Room -- critical patients treated first, not arrival order
  • OS Scheduling -- high-priority processes preempt low-priority ones
  • Dijkstra's Algorithm -- always expand the nearest unvisited node

Analogy

A regular queue is like a deli counter with numbered tickets. A priority queue is like an ER triage: the sickest patient goes first, even if they just walked in.

Coming up: heaps, the efficient way to implement priority queues!

16 / 18

Common Pitfalls

1. Forgetting Circular Wrap-Around

Writing rear++ instead of rear = (rear + 1) % N. This causes an ArrayIndexOutOfBounds when rear exceeds the array length.

// WRONG rear++; // CORRECT rear = (rear + 1) % data.length;

2. Dequeuing from an Empty Queue

Always check isEmpty() before calling dequeue(). An empty dequeue is undefined behavior in some implementations, or throws an exception in others.

// WRONG E item = queue.dequeue(); // CORRECT if (!queue.isEmpty()) { E item = queue.dequeue(); }

3. Confusing Front and Rear

Enqueue at rear, dequeue from front. Mixing these up produces a stack-like structure or corrupted data.

REMEMBER: enqueue -> REAR (back of the line) dequeue -> FRONT (served next)

4. Full vs Empty Confusion

In a circular array without a size counter, both full and empty states have front == rear. Always use a count variable or waste-one-slot strategy.

5. Forgetting to Null References

After dequeuing, set data[front] = null to avoid memory leaks (loitering references prevent garbage collection).

17 / 18

Summary & Cheat Sheet

Queue at a Glance

PropertyValue
Access PolicyFIFO
enqueueO(1)
dequeueO(1)
front / peekO(1)
isEmpty / sizeO(1)

Implementations Compared

Array (Circular)Linked List
enqueueO(1) amortizedO(1)
dequeueO(1)O(1)
MemoryCompact, cache-friendlyExtra pointer overhead
ResizeO(n) when fullNever needed

Key Formulas (Circular Array)

Advance front: f = (f + 1) % N Advance rear: r = (r + 1) % N Is empty: size == 0 Is full: size == N

Family of Queue ADTs

+-------------------+ | Deque | Both ends | +-------------+ | | | Queue | | FIFO (rear in, front out) | +-------------+ | | +-------------+ | | | Stack | | LIFO (one end only) | +-------------+ | +-------------------+ Priority Queue: a separate concept (not FIFO -- based on key/priority)

Applications Recap

  • BFS -- level-order graph/tree traversal
  • Print spooling -- fair job scheduling
  • Hot Potato -- circular elimination games
  • Buffering -- producer-consumer patterns
  • OS scheduling -- round-robin CPU scheduling
18 / 18