Topics › Stacks and Queues › Learning
Implement Queue using Arrays
Java source ↗ Edit this article ↗
Jump to code:
Problem
Design a queue of integers whose storage is a plain array. A queue is a first in, first out container: elements leave in the same order they arrived. Support these operations:
push(x)(enqueue): addxat the rear.pop()(dequeue): remove and return the element at the front.peek(): return the front element without removing it.isEmpty(): is the queue empty?size(): how many elements are stored?
Constraints: up to 10^5 operations, values fit in a 32-bit int. pop and peek on an empty queue are errors; the code throws NoSuchElementException. A fixed-capacity queue must reject a push when it is full.
Example 1
push(1), push(2), push(3), peek(), pop(), pop(), size(), isEmpty()
output: 1, 1, 2, 1, false // 1 arrived first, so it leaves first
Example 2 (capacity 2)
push(1), push(2), pop(), push(3), peek(), pop(), pop()
output: 1, 2, 2, 3 // the slot freed by pop() must be reusable
All three implementations below satisfy this interface so that one test script can drive them:
interface IntQueue {
void push(int x);
int pop();
int peek();
boolean isEmpty();
int size();
}
Approach 1: Front fixed at index 0, shift on pop
Intuition. The simplest layout keeps the oldest element at data[0] and the newest at data[count - 1]. Pushing appends at data[count]. Popping removes data[0], but then every other element has to move one slot to the left so that the new oldest element is again at index 0. That shift is the cost of insisting on a fixed front.
Algorithm.
- Allocate
data = new int[capacity]and setcount = 0. push(x): ifcount == capacitythrow; otherwisedata[count++] = x.pop(): ifcount == 0throw; rememberdata[0], copydata[i]todata[i - 1]fori = 1 .. count - 1, decrementcount, return the remembered value.peek()returnsdata[0];isEmpty()iscount == 0;size()iscount.
static class ShiftingArrayQueue implements IntQueue {
private final int[] data;
private int count = 0; // elements live in data[0 .. count-1], oldest at 0
ShiftingArrayQueue(int capacity) {
data = new int[capacity];
}
public void push(int x) {
if (count == data.length) throw new IllegalStateException("queue overflow");
data[count++] = x;
}
public int pop() {
if (count == 0) throw new NoSuchElementException("queue underflow");
int front = data[0];
for (int i = 1; i < count; i++) data[i - 1] = data[i]; // O(n) shift
count--;
return front;
}
public int peek() {
if (count == 0) throw new NoSuchElementException("queue is empty");
return data[0];
}
public boolean isEmpty() {
return count == 0;
}
public int size() {
return count;
}
}
Complexity. Time O(1) for push, peek, isEmpty, size and O(n) for pop because of the shift. Space O(capacity).
A variant that avoids the shift keeps a front index that only moves right and never wraps. That makes pop O(1) but leaks space: after k pops the first k slots are dead, and once front + count reaches the end the queue is “full” even if it holds a single element. The fix for that leak is the circular buffer.
Approach 2: Circular buffer
Intuition. Treat the array as a ring: the slot after data[capacity - 1] is data[0] again. Keep the index front of the oldest element and the count of stored elements. The rear slot, where the next push goes, is then (front + count) % capacity, so it does not need its own variable, and the question “is the queue full or empty?” is answered by count rather than by comparing two indices that could coincide in both situations. Popping just advances front by one, modulo the capacity. No element ever moves.
Algorithm.
- Allocate
data = new int[capacity], setfront = 0andcount = 0. push(x): ifcount == capacitythrow; otherwise writextodata[(front + count) % capacity]and incrementcount.pop(): ifcount == 0throw; rememberdata[front], setfront = (front + 1) % capacity, decrementcount, return the value.peek()returnsdata[front];isEmpty()iscount == 0;size()iscount.
static class CircularArrayQueue implements IntQueue {
private final int[] data;
private int front = 0; // index of the oldest element
private int count = 0; // how many slots are in use
CircularArrayQueue(int capacity) {
data = new int[capacity];
}
public void push(int x) {
if (count == data.length) throw new IllegalStateException("queue overflow");
data[(front + count) % data.length] = x; // wraps around to reuse freed slots
count++;
}
public int pop() {
if (count == 0) throw new NoSuchElementException("queue underflow");
int x = data[front];
front = (front + 1) % data.length;
count--;
return x;
}
public int peek() {
if (count == 0) throw new NoSuchElementException("queue is empty");
return data[front];
}
public boolean isEmpty() {
return count == 0;
}
public int size() {
return count;
}
}
Dry run on example 2 with capacity 2:
| operation | data | front | count | rear = (front+count)%2 | returns |
|---|---|---|---|---|---|
| push(1) | [1, _] | 0 | 1 | 1 | |
| push(2) | [1, 2] | 0 | 2 | 0 | |
| pop() | [1, 2] | 1 | 1 | 0 | 1 |
| push(3) | [3, 2] | 1 | 2 | 1 | |
| peek() | [3, 2] | 1 | 2 | 2 | |
| pop() | [3, 2] | 0 | 1 | 2 | |
| pop() | [3, 2] | 1 | 0 | 3 |
The push of 3 wrapped around into index 0, the slot that pop had freed. The shifting queue would have reached the same answers by moving 2 to index 0 first.
Complexity. Time O(1) for every operation: constant index arithmetic plus one array access. Space O(capacity).
Approach 3: Growable circular buffer
Intuition. The ring removes the space leak but still has a hard capacity. Doubling the array when it is full gives amortised O(1) pushes, exactly as for a growable stack, with one twist: the elements may be wrapped around, so copying data slot by slot would scramble their order. Instead, copy them in queue order starting at front, so that the oldest lands at index 0 of the new array, and reset front to 0.
Algorithm.
- Start with
data = new int[2],front = 0,count = 0. push(x): ifcount == data.length, callgrow(): allocate twice the length, copy elementifromdata[(front + i) % oldLength]tobigger[i], then replacedataand setfront = 0. Then push as in approach 2.- All other operations are identical to approach 2.
static class DynamicCircularQueue implements IntQueue {
private int[] data = new int[2];
private int front = 0;
private int count = 0;
public void push(int x) {
if (count == data.length) grow();
data[(front + count) % data.length] = x;
count++;
}
private void grow() {
int[] bigger = new int[data.length * 2];
for (int i = 0; i < count; i++) bigger[i] = data[(front + i) % data.length]; // unwrap: oldest lands at 0
data = bigger;
front = 0;
}
public int pop() {
if (count == 0) throw new NoSuchElementException("queue underflow");
int x = data[front];
front = (front + 1) % data.length;
count--;
return x;
}
public int peek() {
if (count == 0) throw new NoSuchElementException("queue is empty");
return data[front];
}
public boolean isEmpty() {
return count == 0;
}
public int size() {
return count;
}
int capacity() {
return data.length;
}
}
Complexity. Time amortised O(1) per operation; a push that grows the array costs O(n) but happens only after n pushes since the last growth. Space O(n).
Edge cases and pitfalls
- Full versus empty. In a ring with only
frontandrearindices, both conditions look likefront == rear. Storingcount(or sacrificing one slot) resolves the ambiguity. Every method above testscount, never the indices. - Wraparound. Forgetting the
% capacityonfrontor on the rear computation is the classic bug; it shows up only when the queue has been drained and refilled, so test exactly that sequence (test 2 in the Java file uses capacity 2 to force it early). - Growing a wrapped buffer.
Arrays.copyOfon a wrapped ring copies the elements in index order, not queue order, and leaves the pushed-later elements before the pushed-earlier ones. Always unwrap fromfront. - Underflow.
popandpeekmust checkcount == 0before readingdata[front]; the slot holds a stale value, not an error, so without the check the bug is silent. - Negative modulo.
frontandcountare never negative here, so%is safe. If you ever compute(index - 1) % capacity, addcapacityfirst.
Related problems
- Implement Stack using Arrays is the one-ended version where no wraparound is needed.
- Implement queue using Linkedlist gets unbounded size with
O(1)operations and no copying. - Implement Queue using Stack builds the same interface out of LIFO operations only.
- Sliding Window Maximum uses a double-ended queue, the natural extension of the ring buffer.
- LRU Cache is a queue of recently used keys with random access.