Skip to content
Shafin Chowdhury
Go back

Stack Using Array: Fixed-Size Implementation, Overflow & Dry Run

Software Developer & Computer Science Student

Published:
11 min read
Edit page
Table of Contents11 topics

In our previous tutorial on Stack Using Vector in C++, vector handled all the index tracking and memory resizing for us behind the scenes.

In this guide, we will build an array-based Stack step by step, understand why the famous topIndex = -1 initialization exists, visualize how elements move in and out of the array, and trace a complete dry run.

Part of the Stack Series: This article is Step 3 of our Stack in C++: Complete Beginner-to-Advanced Guide.


1. Why Implement a Stack Using an Array?

Building a Stack on top of a fixed-size C++ array gives you three major benefits:

  1. You Learn the True Mechanics: Without STL helper functions like push_back(), you control the exact array index where the top of the stack lives.
  2. Zero Dynamic Memory Overhead: A fixed-size array reserves all of its memory up front. There are no slow heap allocations or resizing pauses during execution.
  3. Maximum Hardware Speed: Array elements sit in contiguous memory slots, making array-based stacks the fastest possible implementation when the maximum number of elements is known ahead of time.

2. Understanding the top Variable

An array of size 5 always has 5 slots in memory (arr[0] through arr[4]), even before you put any meaningful numbers into it.

So how does our program know how many elements are currently in the stack and which slot holds the newest element?

Inside our Stack class, we declare the fixed-size array alongside a single integer variable—named topIndex—to store the array index of the topmost element:

const int MAX_SIZE = 5;

class Stack {
private:
    int arr[MAX_SIZE]; // Fixed-size array that holds stack elements
    int topIndex;      // Tracks the index of the current top element
};

Here is how topIndex tracks the active stack:

  • When the stack has 1 element, topIndex is 0 (arr[0]).
  • When the stack has 2 elements, topIndex is 1 (arr[1]).
  • When the stack has 3 elements, topIndex is 2 (arr[2]).
Array Indices:     [0]     [1]     [2]     [3]     [4]
                 +-----+ +-----+ +-----+ +-----+ +-----+
Array Values:    |  10 | |  20 | |  30 | |  ?  | |  ?  |
                 +-----+ +-----+ +-----+ +-----+ +-----+
                                    ^
                                topIndex = 2
                           (3 valid elements: indices 0, 1, 2)

3. Why Do We Initialize top = -1?

When a new Stack object is created, its constructor initializes topIndex to -1:

class Stack {
private:
    int arr[MAX_SIZE];
    int topIndex;

public:
    // Constructor: initialize topIndex to -1 (empty stack)
    Stack() {
        topIndex = -1;
    }
};

This is one of the most common questions beginners ask: Why do we set topIndex = -1 when creating an empty stack, instead of 0?

Think about how C++ arrays are indexed:

  • The first valid slot of any C++ array is index 0.
  • If we initialized topIndex = 0, that would mean “there is already a valid element sitting at arr[0]!”
  • We wouldn’t be able to tell the difference between an empty stack and a stack containing 1 element at arr[0].

By initializing topIndex = -1:

  1. -1 is not a valid array index, so it clearly signals that the stack is completely empty.
  2. When we push our very first element, we increment topIndex by 1 (-1 + 1 = 0), which lands right on index 0!
Empty Stack (topIndex = -1):
   (-1)      [0]     [1]     [2]     [3]     [4]
    ^      +-----+ +-----+ +-----+ +-----+ +-----+
 topIndex  |     | |     | |     | |     | |     |
 (Outside) +-----+ +-----+ +-----+ +-----+ +-----+

After First push(10) -> topIndex becomes -1 + 1 = 0:
             [0]     [1]     [2]     [3]     [4]
           +-----+ +-----+ +-----+ +-----+ +-----+
           |  10 | |     | |     | |     | |     |
           +-----+ +-----+ +-----+ +-----+ +-----+
              ^
         topIndex = 0

4. How Insertion (push) Works

Here is the C++ function that inserts a new element onto the top of our array-based stack:

// Insert an element onto the top of the stack
void push(int value) {
    if (isFull()) {
        cout << "Stack Overflow! Cannot push " << value << " because stack is full." << endl;
        return;
    }
    topIndex++;
    arr[topIndex] = value;
    cout << value << " pushed into stack at index " << topIndex << "." << endl;
}

Look at how push(int value) executes a strict two-step sequence:

  1. Step 1 (Check if full): First, it calls isFull() to verify that topIndex has not already reached the last array index (MAX_SIZE - 1). If the array is full, it prints a Stack Overflow warning and exits early with return;.
  2. Step 2 (Increment, then store):
    • First, move topIndex one slot to the right: topIndex++;.
    • Second, place the new value into that slot: arr[topIndex] = value;.
Before push(25) (topIndex = 1):
Index:      [0]     [1]     [2]     [3]     [4]
          +-----+ +-----+ +-----+ +-----+ +-----+
arr:      |  10 | |  20 | |     | |     | |     |
          +-----+ +-----+ +-----+ +-----+ +-----+
                     ^
                topIndex = 1

Step A: topIndex++ (topIndex becomes 2)
Step B: arr[2] = 25

After push(25) (topIndex = 2):
Index:      [0]     [1]     [2]     [3]     [4]
          +-----+ +-----+ +-----+ +-----+ +-----+
arr:      |  10 | |  20 | |  25 | |     | |     |
          +-----+ +-----+ +-----+ +-----+ +-----+
                             ^
                        topIndex = 2

5. How Deletion (pop) Works

Here is the C++ function that removes the top element from our array-based stack:

// Remove the top element from the stack
void pop() {
    if (isEmpty()) {
        cout << "Stack Underflow! Cannot pop from an empty stack." << endl;
        return;
    }
    int removedValue = arr[topIndex];
    topIndex--;
    cout << removedValue << " popped from stack." << endl;
}

Removing the top element follows two simple steps:

  1. Step 1 (Check if empty): Calls isEmpty() to verify that topIndex is not -1. If the stack is empty, it prints a Stack Underflow message and returns immediately.
  2. Step 2 (Read and Decrement topIndex):
    • Saves arr[topIndex] into removedValue so we can print which number was removed.
    • Moves topIndex one slot to the left using topIndex--;.
Before pop() (topIndex = 2):
Index:      [0]     [1]     [2]     [3]     [4]
          +-----+ +-----+ +-----+ +-----+ +-----+
arr:      |  10 | |  20 | |  25 | |     | |     |
          +-----+ +-----+ +-----+ +-----+ +-----+
                             ^
                        topIndex = 2

After pop() -> topIndex-- (topIndex becomes 1):
Index:      [0]     [1]     [2]     [3]     [4]
          +-----+ +-----+ +-----+ +-----+ +-----+
arr:      |  10 | |  20 | |(25) | |     | |     |
          +-----+ +-----+ +-----+ +-----+ +-----+
                     ^       ^
                topIndex = 1 (Ignored / out of bounds of active stack)

6. Why pop() Does Not Physically Delete the Array Element

Look closely at slot [2] in the diagram above after pop(). The number 25 is still physically sitting in RAM at arr[2]! Why didn’t we write arr[topIndex] = 0; inside pop()?

// Inside pop():
int removedValue = arr[topIndex];
topIndex--; // We only move the index! We do NOT need arr[topIndex + 1] = 0;

Because topIndex defines the boundary of our stack:

  • Any slot at an index greater than topIndex is considered outside the stack (“inactive memory”).
  • top() only reads arr[topIndex] (arr[1]).
  • display() only loops from topIndex down to 0.
  • The very next time you call push(99), topIndex increments back to 2 and overwrites arr[2] with 99:
    topIndex++;          // topIndex moves from 1 back to 2
    arr[topIndex] = 99;  // Overwrites the old 25 at arr[2] with 99!

Skipping an unnecessary write like arr[topIndex] = 0 saves CPU work while keeping the stack 100% logically accurate.


7. Stack Overflow (isFull) and Stack Underflow (isEmpty)

When working with a fixed-size array of size MAX_SIZE (whose valid indices are 0 to MAX_SIZE - 1), two boundary errors can occur. We write two dedicated helper functions—isFull() and isEmpty()—to guard against them:

1. Stack Overflow (Pushing into a Full Stack)

// Check if the stack has reached its maximum capacity
bool isFull() {
    if (topIndex == MAX_SIZE - 1) {
        return true;
    } else {
        return false;
    }
}

When topIndex == MAX_SIZE - 1, every single slot in the array is occupied. If you try to push() another item without checking isFull(), topIndex becomes MAX_SIZE, and arr[topIndex] writes into illegal memory outside the array!

2. Stack Underflow (Popping from an Empty Stack)

// Check if the stack is completely empty
bool isEmpty() {
    if (topIndex == -1) {
        return true;
    } else {
        return false;
    }
}

When topIndex == -1, there are zero elements in the stack. If you try to pop() or read arr[topIndex] without checking isEmpty(), your program will attempt to access arr[-1], which is invalid memory!

  topIndex == -1                             topIndex == MAX_SIZE - 1
 (STACK UNDERFLOW                            (STACK OVERFLOW
  isEmpty() is true)                          isFull() is true)
        |                                            |
        v                                            v
      (-1)      [0]     [1]     [2]     [3]     [4] (MAX_SIZE = 5)
              +-----+ +-----+ +-----+ +-----+ +-----+
              |  10 | |  20 | |  30 | |  40 | |  50 |
              +-----+ +-----+ +-----+ +-----+ +-----+

8. Inspecting the Top (top), size(), and display()

To complete our Stack toolkit, we need three read-only functions that inspect the stack without modifying it:

1. top() — Peeking at the Top Element

// Return the top element without removing it
int top() {
    if (isEmpty()) {
        cout << "Stack is empty! No top element." << endl;
        return -1;
    }
    return arr[topIndex];
}

First checks isEmpty(). If the stack is not empty, it directly returns the value sitting at arr[topIndex] without changing topIndex.

2. size() — Counting Active Elements

// Return current number of elements in the stack
int size() {
    return topIndex + 1;
}

Because C++ array indices start at 0, the number of elements in the stack is always topIndex + 1. (When empty, -1 + 1 = 0; when topIndex is 2, 2 + 1 = 3 elements.)

3. display() — Printing from Top to Bottom

// Print all elements currently in the stack from top to bottom
void display() {
    if (isEmpty()) {
        cout << "Stack is empty." << endl;
        return;
    }
    cout << "Stack elements (Top to Bottom): ";
    for (int i = topIndex; i >= 0; i--) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

Starts a loop variable i at topIndex (the newest element) and counts downward to 0 (the oldest element at the bottom), printing only the active stack elements.


9. Complete C++ Implementation: Stack Using a Fixed-Size Array

Below is the complete, beginner-friendly C++ implementation of a Stack class using a fixed-size array of capacity 5. No STL containers are used.

#include <iostream>

using namespace std;

const int MAX_SIZE = 5;

class Stack {
private:
    int arr[MAX_SIZE];
    int topIndex;

public:
    // Constructor: initialize topIndex to -1 (empty stack)
    Stack() {
        topIndex = -1;
    }

    // Check if the stack is completely empty
    bool isEmpty() {
        if (topIndex == -1) {
            return true;
        } else {
            return false;
        }
    }

    // Check if the stack has reached its maximum capacity
    bool isFull() {
        if (topIndex == MAX_SIZE - 1) {
            return true;
        } else {
            return false;
        }
    }

    // Insert an element onto the top of the stack
    void push(int value) {
        if (isFull()) {
            cout << "Stack Overflow! Cannot push " << value << " because stack is full." << endl;
            return;
        }
        topIndex++;
        arr[topIndex] = value;
        cout << value << " pushed into stack at index " << topIndex << "." << endl;
    }

    // Remove the top element from the stack
    void pop() {
        if (isEmpty()) {
            cout << "Stack Underflow! Cannot pop from an empty stack." << endl;
            return;
        }
        int removedValue = arr[topIndex];
        topIndex--;
        cout << removedValue << " popped from stack." << endl;
    }

    // Return the top element without removing it
    int top() {
        if (isEmpty()) {
            cout << "Stack is empty! No top element." << endl;
            return -1;
        }
        return arr[topIndex];
    }

    // Return current number of elements in the stack
    int size() {
        return topIndex + 1;
    }

    // Print all elements currently in the stack from top to bottom
    void display() {
        if (isEmpty()) {
            cout << "Stack is empty." << endl;
            return;
        }
        cout << "Stack elements (Top to Bottom): ";
        for (int i = topIndex; i >= 0; i--) {
            cout << arr[i] << " ";
        }
        cout << endl;
    }
};

int main() {
    Stack myStack;

    // 1. Test pushing elements up to capacity (MAX_SIZE = 5)
    myStack.push(10);
    myStack.push(20);
    myStack.push(30);
    myStack.push(40);
    myStack.push(50);

    // 2. Try pushing a 6th element to trigger Stack Overflow
    myStack.push(60);

    cout << "---" << endl;
    myStack.display();
    cout << "Current Top element: " << myStack.top() << endl;
    cout << "Current Stack size: " << myStack.size() << endl;

    cout << "---" << endl;

    // 3. Pop two elements and check the updated state
    myStack.pop();
    myStack.pop();
    myStack.display();
    cout << "New Top element: " << myStack.top() << endl;

    return 0;
}

Output

10 pushed into stack at index 0.
20 pushed into stack at index 1.
30 pushed into stack at index 2.
40 pushed into stack at index 3.
50 pushed into stack at index 4.
Stack Overflow! Cannot push 60 because stack is full.
---
Stack elements (Top to Bottom): 50 40 30 20 10
Current Top element: 50
Current Stack size: 5
---
50 popped from stack.
40 popped from stack.
Stack elements (Top to Bottom): 30 20 10
New Top element: 30

10. Quick Reference Summary of Member Functions

Let’s review how all the member functions inside class Stack fit together:

  1. Stack() (Constructor): Runs automatically when Stack myStack; is created. It sets topIndex = -1 so the stack starts empty.
  2. isEmpty(): Compares topIndex with -1. If topIndex == -1, no elements are in the stack, so it returns true.
  3. isFull(): Compares topIndex with MAX_SIZE - 1 (4 when MAX_SIZE is 5). If topIndex == 4, all 5 slots (0 through 4) are full, so it returns true.
  4. push(int value): First calls isFull(). If there is room, it increments topIndex by 1 and stores value at arr[topIndex].
  5. pop(): First calls isEmpty(). If there is at least one element, it reads arr[topIndex] (for the confirmation message) and decrements topIndex by 1.
  6. top(): Checks isEmpty(). If not empty, it returns arr[topIndex] without changing topIndex.
  7. size(): Since array indices start at 0, the number of active elements is always topIndex + 1 (for example, when topIndex is -1, size is 0; when topIndex is 2, size is 3).
  8. display(): Loops backwards using index i starting from topIndex down to 0, printing each active element from Top to Bottom.

11. Complete Dry Run

Let’s trace a smaller sequence of operations step by step on a stack with MAX_SIZE = 3:

StepOperation CalledtopIndex BeforeAction TakentopIndex AfterActive Stack (0 .. topIndex)Output / Return
1Stack s;—Initialize topIndex = -1-1[ ] (Empty)—
2s.push(5);-1topIndex++ →\rightarrow 0; arr[0] = 50[5]5 pushed at index 0
3s.push(15);0topIndex++ →\rightarrow 1; arr[1] = 151[5, 15]15 pushed at index 1
4s.top();1Return arr[1]1[5, 15]Returns 15
5s.pop();1Read arr[1], then topIndex-- →\rightarrow 00[5]15 popped
6s.push(25);0topIndex++ →\rightarrow 1; overwrites arr[1] = 251[5, 25]25 pushed at index 1
7s.pop();1topIndex-- →\rightarrow 00[5]25 popped
8s.pop();0topIndex-- →\rightarrow -1-1[ ] (Empty)5 popped
9s.pop();-1isEmpty() is true!-1[ ] (Empty)Stack Underflow!

12. Time and Space Complexity

Because an array allows direct O(1)O(1) access to any index, and we never shift elements, every core stack operation runs in constant time:

OperationTime ComplexityExplanation
push(value)O(1)O(1)Increments topIndex and assigns arr[topIndex] in one step.
pop()O(1)O(1)Decrements topIndex in one step.
top()O(1)O(1)Reads arr[topIndex] directly.
isEmpty()O(1)O(1)Single integer comparison (topIndex == -1).
isFull()O(1)O(1)Single integer comparison (topIndex == MAX_SIZE - 1).
display()O(n)O(n)Prints all nn active elements from topIndex down to 0.
  • Space Complexity: O(MAX_SIZE)O(\text{MAX\_SIZE}) (or O(n)O(n) where nn is the fixed capacity of the array), because all MAX_SIZE array slots are reserved in memory as soon as the Stack object is created.

13. Advantages, Disadvantages, and Limitations

Advantages

  • Simple to Implement: Requires no pointers or manual new/delete memory management.
  • Fastest Execution Speed: Zero heap allocation overhead and optimal CPU cache locality.
  • Predictable Memory Footprint: Memory usage is fixed and known at compile time.

Disadvantages & Limitations of Fixed-Size Arrays

  1. Fixed Capacity (Risk of Stack Overflow): You must guess MAX_SIZE before the program runs. If your program suddenly needs MAX_SIZE + 1 elements, the stack fails with Stack Overflow.
  2. Wasted Memory: If you set MAX_SIZE = 10000 to be “safe,” but your program only pushes 10 elements at a time, 9,990 integer slots sit completely wasted in memory.

How can we get a Stack that grows dynamically one element at a time without ever wasting unused array slots or hitting a fixed limit? The answer is a Linked List—which we will build in the next article!


14. Frequently Asked Questions (FAQ)

Why is the full condition topIndex == MAX_SIZE - 1 instead of topIndex == MAX_SIZE?

Because C++ arrays use 0-based indexing. An array of size 5 has indices 0, 1, 2, 3, 4. The last valid index is 5 - 1 = 4. When topIndex reaches 4, all 5 slots are full.

How can I find the current number of elements in an array-based stack?

The number of elements is always topIndex + 1. When the stack is empty (topIndex = -1), -1 + 1 = 0. When arr[0] through arr[3] are filled (topIndex = 3), 3 + 1 = 4 elements.

Can we implement two stacks inside a single array?

Yes! This is a classic interview problem: start Stack 1 at the left end (top1 = -1, growing rightward) and Stack 2 at the right end (top2 = MAX_SIZE, growing leftward). Both stacks can grow freely until top1 + 1 == top2.


Summary

  • An array-based Stack uses an integer topIndex to track the index of the newest element.
  • topIndex starts at -1 to indicate an empty stack.
  • push(x) checks isFull(), increments topIndex, and sets arr[topIndex] = x.
  • pop() checks isEmpty() and decrements topIndex (leaving the old value in memory to be overwritten later).
  • isFull() is true when topIndex == MAX_SIZE - 1; isEmpty() is true when topIndex == -1.

Continue Learning in the Stack Series

Ready to remove the fixed-size limitation and learn how pointer-based stacks work? Move on to Step 4!


Edit page
Share this post:

Related Articles

Continue exploring related topics and foundational concepts.

9 min read

Queue vs Stack in C++: LIFO vs FIFO, STL & Practical Comparison

Understand the difference between Queue and Stack in C++. Compare FIFO vs LIFO ordering, insertion and deletion ends, accessible elements, std::queue vs std::stack in C++ STL, visual element traces, time complexity, and when to use each.

Read Article
8 min read

Queue Implementation Using Array in C++: Linear & Circular Queue Guide

Learn how to implement a Queue from scratch using a fixed-size array in C++. Master front and rear index management, enqueue and dequeue operations, Queue Overflow and Underflow, visual dry runs, linear Queue limitations, and Circular Queue improvements.

Read Article
12 min read

Stack in C++: Complete Beginner's Guide

Learn the Stack data structure in C++ from scratch. Understand LIFO (Last In, First Out), core stack operations (push, pop, top, empty, size), std::stack in STL, real-world applications, and time complexity.

Read Article

Previous Post
Stack Using Linked List in C++: Dynamic Node & Pointer Implementation
Next Post
Stack Using Vector in C++: Step-by-Step Class Implementation