SEN2211 · Week 7 · Queues

Queue: with a circular array and with a linked list

FIFO logic, enqueue/dequeue/peek, a circular buffer that prevents the array from drifting toward the start, and a linked queue with head/tail. The exam asks for the raw state of the array and the front/rear values after a sequence of operations.

queueFIFOenqueuedequeuefrontrearcircular bufferwrap-aroundmodulolinked queuequeue overflowqueue underflow

A queue is the checkout line at a supermarket: you join at the back, the one in front leaves. The first to arrive is served first: FIFO (First In, First Out). Printer queues, an operating system’s job queue, packets in a network and matchmaking queues in games are all queues. The only difference from a stack is which end you remove from, but implementing it is more work: you have to track both ends. On the exam, the raw state of a circular array (what is in which cell, what front and rear are) is asked almost every year.

Golden rules:

  1. Add at the back (enqueue), remove from the front (dequeue).
  2. Don’t shift elements in the array. Advance the front and rear indices, and wrap around to the start with % when you reach the end.
  3. front == rear can mean both empty and full. Keep a count to tell them apart.
  4. In a linked queue, when the last element comes out, set tail to null too.

The queue ADT

OperationWhat it doesTime
enqueue(x)adds x at the backO(1)
dequeue()removes and returns the front elementO(1)
peek() (or front())returns the front element, doesn’t remove itO(1)
isEmpty(), size()statusO(1)

Comparison with a stack: if 1, 2, 3 are inserted in order and then all are removed

stack: 3 2 1 | queue: 1 2 3

Why isn’t a simple array enough?

The first idea: always keep the front at index 0, and on removal shift everyone one step left. It works correctly, but every dequeue does n − 1 moves. The total cost of removing n elements one by one:

n=1000 shifts=499500
n=2000 shifts=1999000
n=4000 shifts=7998000

When n doubles the cost quadruples: O(n²) in total, O(n) per operation. If you advance the front index instead of shifting, this time the start of the array is wasted, and when rear reaches the end it looks as though there is no room left. The solution: think of the array as a ring.

The circular array (circular buffer)

When an index reaches the end it wraps to 0: i = (i + 1) % capacity. For capacity 4 the index order is 0, 1, 2, 3, 0, 1, …

class ArrayQueue {
    private int[] data;
    private int front = 0;   // index of the first element
    private int rear = 0;    // the next free slot
    private int count = 0;

    ArrayQueue(int capacity) { data = new int[capacity]; }

    boolean isEmpty() { return count == 0; }
    boolean isFull()  { return count == data.length; }

    void enqueue(int x) {
        if (isFull()) throw new RuntimeException("Queue overflow");
        data[rear] = x;
        rear = (rear + 1) % data.length;
        count++;
    }

    int dequeue() {
        if (isEmpty()) throw new RuntimeException("Queue underflow");
        int x = data[front];
        front = (front + 1) % data.length;
        count--;
        return x;
    }

    int peek() {
        if (isEmpty()) throw new RuntimeException("Queue underflow");
        return data[front];
    }
}

PITFALL · the meaning of rear. In some books rear is the index of the last element, in others the next free slot. On this page it is the next free slot. On the exam, read the definition in the question; the formulas shift by one accordingly.

Worked trace (capacity 4)

ArrayQueue q = new ArrayQueue(4);
q.enqueue(10); q.enqueue(20); q.enqueue(30);
System.out.println(q.dequeue());
System.out.println(q.raw());
q.enqueue(40); q.enqueue(50);
System.out.println(q.raw());
System.out.println(q.dequeue() + " " + q.dequeue());
System.out.println(q.raw());
10
[10, 20, 30, 0] front=1 rear=3 count=2
[50, 20, 30, 40] front=1 rear=1 count=4
20 30
[50, 20, 30, 40] front=3 rear=1 count=2
enqueue 10,20,30   [10,20,30, _]  front=0 rear=3 count=3
dequeue → 10       [10,20,30, _]  front=1 rear=3 count=2   (10 stayed in the array)
enqueue 40         [10,20,30,40]  front=1 rear=0 count=3   (rear 3 → (3+1)%4 = 0)
enqueue 50         [50,20,30,40]  front=1 rear=1 count=4   (written over the 10, full)
dequeue → 20       front=2
dequeue → 30       front=3, count=2; the queue logically: 40, 50

In the final state the queue consists of 40 and 50. To read from the front, start at front and advance count times with (i + 1) % cap: index 3 (40), index 0 (50).

TIP · Raw array questions. When the exam asks “what are the contents of the array?”, the removed elements are still written in the array. Only a new enqueue overwrites them. At each step write four things: array, front, rear, count.

Queue with a linked list (linked queue)

In a singly linked list, insertion at the head and deletion at the head are O(1), insertion at the end is O(1) if there is a tail, and deletion from the end is O(n). Therefore: insert at the tail, remove at the head.

class LinkedQueue {
    private static class Node { int data; Node next; Node(int d) { data = d; } }
    private Node head, tail;   // head = front, tail = back

    boolean isEmpty() { return head == null; }

    void enqueue(int x) {
        Node n = new Node(x);
        if (tail == null) head = tail = n;   // empty queue
        else { tail.next = n; tail = n; }
    }

    int dequeue() {
        if (head == null) throw new RuntimeException("Queue underflow");
        int x = head.data;
        head = head.next;
        if (head == null) tail = null;       // the queue became empty
        return x;
    }
}

If the line if (head == null) tail = null; is forgotten: after the queue empties, tail keeps pointing to the old node, the next enqueue is linked behind that dead node, and head stays null. The queue looks empty forever.

Circular arrayLinked queue
enqueue / dequeueO(1)O(1)
Capacityfixed (or growing)unbounded
Memorycontiguous, empty cellsnext per node
Risk of errorforgetting %, empty/full confusionforgetting to update tail

Queue patterns

Rotating. q.enqueue(q.dequeue()) moves the front element to the back. If you do it k times the queue rotates k steps: 10 20 30 40 50 twice → 30 40 50 10 20.

Reversing. Empty the queue into a stack, then fill it back from the stack into the queue: 1 2 3 4 → 4 3 2 1.

Processing in turn. Taking the front two elements and adding the result to the back sets up a “tournament” or merge operation. The number of elements decreases by one each round; stop when one element remains.

Common mistakes

WrongCorrect
Shifting elements in dequeueAdvance front: O(1)
rear = rear + 1rear = (rear + 1) % data.length
If front == rear it is emptyLook at count; it can be full too
The removed value is erased from the arrayIt stays, and is overwritten later
Removing from the tail in a linked queueRemove from the head, insert at the tail
Leaving the tail when the last element comes outif (head == null) tail = null;
Setting only the tail when inserting into an empty linked queuehead = tail = n
Mixing up queue and stack removal orderA queue preserves order, a stack reverses it

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

What is printed? (q is an empty queue.)

q.enqueue(5); q.enqueue(3);
System.out.print(q.dequeue() + " ");
q.enqueue(8); q.enqueue(1);
System.out.print(q.dequeue() + " ");
System.out.print(q.dequeue() + " ");
q.enqueue(6);
System.out.println(q.peek());
Show answer
5 3 8 1

[5,3] → dequeue 5 → [3] → [3,8,1] → dequeue 3 → dequeue 8 → [1] → [1,6], peek gives the front: 1. If the same operations had been done with a stack (the question in week 5), 3 1 8 6 would have been printed.

Question 2easy

The values 1, 2, 3 are inserted into a stack and into a queue, then everything is removed. What is the removal order for each?

Show answer
stack: 3 2 1, queue: 1 2 3

The stack is LIFO: the last in, 3, comes out first. The queue is FIFO: the first in, 1, comes out first. The queue preserves the order, the stack reverses it.

Question 3medium

A circular array queue has capacity 5, front = rear = count = 0. enqueue writes at rear then rear = (rear + 1) % 5; dequeue reads at front then front = (front + 1) % 5. After the calls below, give the array, front, rear and count.

enqueue(1); enqueue(2); enqueue(3); enqueue(4);
dequeue(); dequeue(); dequeue();
enqueue(5); enqueue(6); enqueue(7);
Show answer
[6, 7, 3, 4, 5], front = 3, rear = 2, count = 4

Four enqueues: [1,2,3,4,_], rear 4. Three dequeues: front 3, count 1 (only 4 remains). enqueue(5) goes to index 4, rear (4+1)%5 = 0. enqueue(6) goes to index 0 (over the 1), rear 1. enqueue(7) goes to index 1, rear 2. The old 3 stays in the array but is not in the queue. The removal order: 4 5 6 7.

Question 4hard

Same circular queue design with capacity 4. What are the array, front, rear and count after these calls? Is the queue full?

for (int i = 1; i <= 4; i++) enqueue(i);
dequeue(); dequeue();
enqueue(5); enqueue(6);
Show answer
[5, 6, 3, 4], front = 2, rear = 2, count = 4; yes, full

[1,2,3,4], rear (3+1)%4 = 0. Two dequeues: front 2. 5 goes to index 0, 6 to index 1, rear 2. front == rear but since count = 4 it is full. front == rear alone doesn't say whether it is empty or full; that is why count is kept.

Question 5easy

A naive array queue keeps the front element at index 0 and shifts all elements left on every dequeue. What is the cost of one dequeue, and why is the circular version better?

Show answer
O(n) per dequeue; circular dequeue is O(1)

Shifting moves n − 1 elements. Removing n elements one by one costs n(n−1)/2 moves in total: 499 500 for n = 1000, 1 999 000 for n = 2000 (n² growth). In a circular array only the front index advances and no element shifts: O(1).

Question 6medium

Write void enqueue(int x) and int dequeue() for a circular array queue with fields int[] data; int front, rear, count; (rear = next free slot).

Show answer
enqueue: check full, data[rear] = x, rear = (rear+1) % length, count++. dequeue: check empty, read data[front], front = (front+1) % length, count--.

void enqueue(int x) {
    if (count == data.length)
        throw new RuntimeException("Queue overflow");
    data[rear] = x;
    rear = (rear + 1) % data.length;   // jump to 0 at the end
    count++;
}

int dequeue() {
    if (count == 0)
        throw new RuntimeException("Queue underflow");
    int x = data[front];
    front = (front + 1) % data.length;
    count--;
    return x;
}
Without % data.length, when rear passes the end of the array you get an ArrayIndexOutOfBoundsException, although there is free space at the start.

Question 7medium

Write int dequeue() for a linked queue with Node head (front) and Node tail (rear). Make sure the queue still works after it becomes empty.

Show answer
Take head.data, head = head.next; if head became null set tail = null.

int dequeue() {
    if (head == null)
        throw new RuntimeException("Queue underflow");
    int x = head.data;
    head = head.next;
    if (head == null)   // the last element came out
        tail = null;
    return x;
}
Insertion is done at the end (tail), removal at the front (head). Both are O(1). If you did the reverse (removal from the end), it would be O(n) in a singly linked list.

Question 8hard

In this buggy linked queue, dequeue forgets to set tail = null when the queue becomes empty. What does the queue contain after the calls below?

void enqueue(int x) {
    Node n = new Node(x);
    if (tail == null) head = tail = n;
    else { tail.next = n; tail = n; }
}
int dequeue() { int x = head.data; head = head.next; return x; }

enqueue(1); dequeue(); enqueue(2); enqueue(3);
Show answer
[] (appears empty; 2 and 3 are lost)

After dequeue, head is null but tail still points to the old node 1. Since tail is not null, enqueue(2) links 2 behind the old node, and head stays null. 3 is added to the same chain. Looking from head, the queue is empty: 2 and 3 are lost.

Question 9medium

The queue contains (front first) 10 20 30 40 50. What does it contain after this loop?

for (int k = 0; k < 2; k++)
    q.enqueue(q.dequeue());
Show answer
30 40 50 10 20

In each round the front element is moved to the back. 10 to the back: 20 30 40 50 10. 20 to the back: 30 40 50 10 20. The pattern of rotating the queue k times.

Question 10medium

The queue contains 1 2 3 4 (front first). What does it contain after this code?

Deque<Integer> st = new ArrayDeque<>();
while (!q.isEmpty()) st.push(q.dequeue());
while (!st.isEmpty()) q.enqueue(st.pop());
Show answer
4 3 2 1

Items come out of the queue in the order 1 2 3 4 and are pushed onto the stack; the top is 4. They come out of the stack in the order 4 3 2 1 and enter the queue. A stack is the standard way to reverse a queue.

Question 11easy

A circular array queue of capacity 3 runs this loop. What is printed?

try {
    for (int i = 1; i <= 4; i++) {
        q.enqueue(i * 10);
        System.out.print(i * 10 + " ");
    }
} catch (RuntimeException e) {
    System.out.println("-> " + e.getMessage());
}
Show answer
10 20 30 -> Queue overflow

Three insertions succeed, and count 3 = capacity. The fourth enqueue is stopped by the full check; the catch runs without printing 40.

Question 12hard

The queue contains 4 9 2 7 (front first). What is printed?

int total = 0;
while (q.size() > 1) {
    int x = q.dequeue();
    int y = q.dequeue();
    q.enqueue(x + y);
    total += x + y;
}
System.out.println(q.peek() + " " + total);
Show answer
22 44

4 + 9 = 13 → [2,7,13], total 13. 2 + 7 = 9 → [13,9], total 22. 13 + 9 = 22 → [22], total 44. The output: 22 44.

Verification: ArrayQueue (a circular array with front, rear, count), LinkedQueue and the shifting NaiveQueue were run on the JDK in verify/SEN2211/W07.java (java W07.java). The raw state of the arrays and the front/rear/count values are taken verbatim from the program's raw() output; the cost of the shifting queue was counted for n = 1000, 2000, 4000 and compared with n²/2.

Review card

QUEUE = FIFO (First In, First Out): enters at the back, leaves from the front enqueue(x) add at the back | dequeue() take from the front and remove | peek() look at the front | isEmpty() all O(1) Shifting array: dequeue O(n) (total n²/2) → DON'T USE CIRCULAR ARRAY: front = first element, rear = next free slot, count enqueue: data[rear] = x; rear = (rear + 1) % cap; count++ dequeue: x = data[front]; front = (front + 1) % cap; count-- empty: count == 0 full: count == cap front == rear can be both empty and full → keep count (or leave 1 cell empty, week 8) dequeue does not erase the value from the array; a new enqueue overwrites it LINKED QUEUE: head = front (removal), tail = back (insertion) enqueue: if tail == null then head = tail = n; otherwise tail.next = n; tail = n dequeue: head = head.next; if head became null then tail = null (DON'T FORGET) Rotate: q.enqueue(q.dequeue()) Reverse: queue → stack → queue