Stacks and Queues · Stacks and Queues Fundamentals

Array-Backed Stack and Queue

Implementing a stack or queue directly on a fixed-size array - a stack via a single moving top index, a queue via two indices (front and rear) that wrap around circularly to reuse freed space.

  • 5 min read
  • 7 practice questions
  • 3 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: a circular deque
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Library stacks and queues grow on demand, but many systems need fixed memory and no allocation: audio buffers, network packet queues, embedded code. Building a stack or queue on a plain array is simple - except that a queue's front keeps moving, and without wrapping around, the freed space at the start is lost.

This skill builds a stack and a queue on a plain array. A stack needs one index. A queue becomes a ring with a front index and a count, so freed slots are reused. A deque adds stepping backwards safely. These are the building blocks of every fixed-memory buffer.

We'll go from simple to more technical:

  1. The pattern: a stack with one index, and a queue as a ring wrapped with modulo.
  2. A second flavor: a circular deque, where stepping the front backwards needs care.
  3. Where ring buffers run (audio, network rings, last-N logs, lock-free queues).
  4. What breaks it: no wrap-around, full versus empty, negative modulo, ambiguous empty markers.

The approach

Java

// Stack: one index
int[] a = new int[capacity];
int top = -1;                                      // -1 = empty
void push(int x) { a[++top] = x; }
int pop()        { return top == -1 ? -1 : a[top--]; }

// Queue: a front index and a count, wrapped with modulo
int front = 0, count = 0;
void enqueue(int x) { a[(front + count) % a.length] = x; count++; }
int dequeue() {
    if (count == 0) return -1;
    int v = a[front];
    front = (front + 1) % a.length;                // wrap: slot 0 follows the last slot
    count--;
    return v;
}

Python

# Stack: one index
a = [0] * capacity                                 # preallocated: the point is fixed memory
top = -1                                           # -1 = empty


def push(x: int) -> None:
    global top
    top += 1
    a[top] = x


def pop() -> int:
    global top
    if top == -1:
        return -1
    top -= 1
    return a[top + 1]


# Queue: a front index and a count, wrapped with modulo
front = count = 0


def enqueue(x: int) -> None:
    global count
    a[(front + count) % len(a)] = x
    count += 1


def dequeue() -> int:
    global front, count
    if count == 0:
        return -1
    v = a[front]
    front = (front + 1) % len(a)                   # wrap: slot 0 follows the last slot
    count -= 1
    return v

The modulo turns the array into a ring, so slots freed at the front are reused at the back - the queue can run forever in fixed memory. The count distinguishes full from empty: with only front and rear indices, both states can look like "front == rear", which is why some implementations waste one slot on purpose instead. Returning -1 for "empty" is safe only when -1 can never be stored; here values are 0 to 10^9.

A second flavor: a circular deque

A deque adds and removes at both ends. Stepping the front backwards needs care, because Java's % keeps the sign of its left operand: (0 - 1) % 5 is -1, an invalid index. (Python's % takes the divisor's sign, so there (0 - 1) % 5 is already 4.)

Java

boolean insertFront(int value) {
    if (size == a.length) return false;
    front = (front - 1 + a.length) % a.length;     // add the capacity before taking the remainder
    a[front] = value;
    size++;
    return true;
}
int getRear() { return size == 0 ? -1 : a[(front + size - 1) % a.length]; }

Python

def insert_front(self, value: int) -> bool:
    if self.size == len(self.a):
        return False
    self.front = (self.front - 1) % len(self.a)    # Python's % is never negative for a positive divisor
    self.a[self.front] = value
    self.size += 1
    return True


def get_rear(self) -> int:
    return -1 if self.size == 0 else self.a[(self.front + self.size - 1) % len(self.a)]

Every index is derived from front and size, so the four operations stay O(1). Math.floorMod(front - 1, a.length) does the same as adding the capacity first.

Where you'll see it

Ring buffers are everywhere in systems code: audio and video buffers, network card receive rings, logging buffers that keep the last N lines, and lock-free queues such as the LMAX Disruptor, which requires a power-of-two size so it can wrap with index & (size - 1) instead of a division. Java's own ArrayDeque is a resizable circular array; Python's collections.deque is a linked list of fixed-size blocks instead - O(1) at both ends, O(n) to index the middle.

Which tool for which job?

You needUse
A stack in fixed memoryan array and a top index
A FIFO queue in fixed memorya ring: front index plus count, wrapped
Both endsa circular deque
Unbounded growtha resizable ring that doubles (ArrayDeque)
The last N items onlya ring that overwrites the oldest (deque(maxlen=N))
One producer, one consumer, no locksever-increasing head and tail counters, masked

Common mistakes

MistakeWhat happensDo this instead
Indices that never wrapthe queue "fills" after capacity enqueueswrap with % capacity
Only front and rear indicesfull and empty look the samekeep a count, or leave one slot empty
(i - 1) % n in Java-1 when i is 0(i - 1 + n) % n or Math.floorMod
-1 as the empty valuea stored -1 looks like emptya value outside the data, or an exception
i & (n - 1) with n not a power of two13 & 5 is 5, but 13 % 6 is 1power-of-two sizes only

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why does an array queue wrap its indices with % capacity?

Show answer

So slots freed by dequeues at the front are reused by later enqueues. Without wrapping, the indices only move forward and run off the end after capacity enqueues, however many were dequeued.

2. How do you tell a full ring buffer from an empty one?

Show answer

Keep a count (or size) alongside the front index - with only front and rear, both states can look the same. Alternatively, leave one slot always unused, so "full" is rear one step behind front.

3. What's wrong with front = (front - 1) % capacity in a deque's insertFront?

Show answer

In Java the remainder keeps the sign of the left operand, so when front is 0 the result is -1 - an invalid index. Use (front - 1 + capacity) % capacity or Math.floorMod.

Key takeaways

  • Stack: one index starting at -1. Queue: front plus count, wrapped with %.
  • A count tells full from empty.
  • Step backwards with (i - 1 + n) % n; Java's % can be negative.
  • An "empty" return value must not be a storable value.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Fixed stackarray + top index
Fixed queuering: front + count
Both endscircular deque
Unboundedring that doubles
Last Noverwrite the oldest
Lock-free SPSCmasked counters

Snippets worth knowing by heart

Java

a[(front + count) % a.length] = x; count++;          // enqueue
front = (front - 1 + a.length) % a.length;           // step back without going negative
a[tail++ & mask] = x;                                // power-of-two ring with counters
boolean full = tail - head == a.length, empty = tail == head;

Python

from collections import deque
last_n = deque(maxlen=1000)                          # overwrites the oldest when full
front = (front - 1) % len(a)                         # Python's % is never negative here

The traps

  • No wrap-around: the queue reports full after capacity enqueues, even after dequeues. Wrap indices with % capacity.
  • Full versus empty: with only front and rear, both look the same. Keep a count, or leave one slot empty.
  • Negative modulo in Java: (i - 1) % n is -1 when i is 0. Use Math.floorMod(i - 1, n).
  • -1 as empty: a stored -1 looks like an empty slot. Use a value outside the data, or throw.
  • Masks with other sizes: i & (n - 1) equals i % n only for power-of-two n. Size the buffer accordingly.

Staff-level points, one line each

  • Ever-increasing counters: empty is tail == head, full is tail - head == size; one writer each makes SPSC queues lock-free.
  • int counters across overflow: tail - head stayed 5. Subtract, never compare.
  • 13 & 5 is 5 but 13 % 6 is 1; -1 & 7 is 7 but -1 % 8 is -1.
  • 1,000,000 pushes: doubling copied 1,048,575 elements, +1,000 slots copied 499,501,000.
  • Full policy: block, drop the new item, or overwrite the oldest.

Say it in an interview

  • "The modulo makes the array a ring, so freed slots get reused."
  • "I keep a count so full and empty can't look the same."
  • "With a power-of-two size I can mask instead of dividing."
  • "When it's full, I decide between backpressure, dropping and overwriting."

Practice

This skill comes with 7 multiple-choice questions that tell you which misconception a wrong answer reveals and 3 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Array-Backed Stack and Queue free

Design Circular Deque

MediumProCoding problemJava · Python

Build a double-ended queue that holds at most k values, stored in a fixed-size array used as a ring (in Python, a list preallocated with [0] * k):

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Array-Backed Stack and Queue free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime