Your One-Stop Solution for Stack Implementation Using Array
TL;DR: A stack is a Last-In, First-Out (LIFO) data structure. An array-based implementation stores elements sequentially in an array, with a top variable tracking the highest occupied position. The core logic stays the same whether you write it in Java, C, or another language.

Choosing the right data structure at the right time matters for every application. Implementing a stack with an array manages data in a predictable LIFO order, which is useful for expression parsing, backtracking, undo operations, and function-call-style logic.

This guide walks through the logic and code for building a functional array-based stack, then implements push, pop, and peek in both Java and C.

How Is a Stack Represented Using an Array?

A stack is a linear data structure that follows the Last-In, First-Out principle. In an array-based implementation, a top variable moves through the array's indices to track the most recently added element.

The top variable is initialized to -1, which signals an empty stack. Each time you add a new element, the top index increases by 1. The stack is full when the top index reaches the array's capacity minus 1.

The array reserves a fixed block of memory when it's created, not incrementally as elements are added. The bottom element sits at index 0, with subsequent elements at indexes 1, 2, 3, and so on. The top variable always holds the index of the current top element, so operations can read or write that position directly without searching the array.

Stack Terminology

A few terms come up throughout this guide:

  • Top: the index of the most recently added element, the only position push and pop touch directly.
  • Capacity: the fixed maximum number of elements the array can hold.
  • Size: the current number of elements in the stack (top + 1).
  • Empty stack: a stack with no elements, indicated when top equals -1.
  • Full stack: a stack where top equals capacity - 1, meaning no more elements can be pushed.
  • Stack overflow: attempting to push onto a full stack.
  • Stack underflow: attempting to pop or peek from an empty stack.

AI-Powered Full Stack Developer ProgramEXPLORE COURSE
Advance Your Full Stack Career!

When to Use an Array-Based Stack

Use an array-based stack when the maximum size is known or predictable, and raw speed matters. Use a linked-list stack or a dynamically resizing implementation when the stack size can grow unpredictably.

Algorithm for Array-Based Stack Implementation

A reliable stack implementation needs a few checks to prevent invalid array access or an incorrect stack state. Whether you implement it in C, Java, or Python, the logic is the same.

Initialization:

  1. Create an integer array with a predefined, fixed capacity.
  2. Set the top variable to -1.

Two small, reusable checks handle state verification before most other operations run:

  • isEmpty() returns true when top equals -1.
  • isFull() returns true when top equals capacity - 1.

Push, pop, and peek each call one of these checks first. This prevents out-of-bounds access and keeps the stack's internal state consistent.

Java Implementation

The complete array-based stack implementation in Java is below. top starts at -1 because the stack begins empty, and -1 is not a valid array index, so isEmpty() can check it with a single comparison instead of tracking a separate count.

public class ArrayStackDemo {
    static class ArrayStack {
        private final int[] stackArray;
        private int top;
        private final int capacity;

        public ArrayStack(int capacity) {
            this.capacity = capacity;
            this.stackArray = new int[capacity];
            this.top = -1;
        }

        public boolean isEmpty() {
            return top == -1;
        }

        public boolean isFull() {
            return top == capacity - 1;
        }

        public int size() {
            return top + 1;
        }

        public int[] contents() {
            int[] result = new int[size()];
            for (int i = 0; i <= top; i++) {
                result[i] = stackArray[i];
            }
            return result;
        }

        public void push(int value) {
            if (isFull()) {
                throw new IllegalStateException("Stack Overflow");
            }
            stackArray[++top] = value;
        }

        public int pop() {
            if (isEmpty()) {
                throw new IllegalStateException("Stack Underflow");
            }
            return stackArray[top--];
        }

        public int peek() {
            if (isEmpty()) {
                throw new IllegalStateException("Stack is empty");
            }
            return stackArray[top];
        }

        public void display() {
            if (isEmpty()) {
                System.out.println("Empty Stack");
                return;
            }
            for (int i = top; i >= 0; i--) {
                System.out.print(stackArray[i] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        ArrayStack stack = new ArrayStack(3);
        stack.push(10);
        stack.push(20);
        stack.push(30);
        stack.display();
        System.out.println("Top element: " + stack.peek());
        System.out.println("Size: " + stack.size());

        try {
            stack.push(40);
        } catch (IllegalStateException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        stack.pop();
        stack.pop();
        stack.pop();
        try {
            stack.pop();
        } catch (IllegalStateException e) {
            System.out.println("Caught: " + e.getMessage());
        }
    }
}

Expected output:

30 20 10 
Top element: 30
Size: 3
Caught: Stack Overflow
Caught: Stack Underflow

C Implementation

The same logic translates directly to C. Since C has no classes, the stack is modeled with a struct holding the array, its capacity, and the top index, with each operation written as a function that takes a pointer to that struct.

One difference from the Java version worth noting: Java's ArrayStack(5) sets capacity at runtime, through the constructor. The C version below uses #define MAX_CAPACITY 5, which fixes the capacity at compile time instead, so it can't be changed without recompiling.

#include <stdio.h>
#include <stdbool.h>

#define MAX_CAPACITY 5

typedef struct {
    int items[MAX_CAPACITY];
    int top;
} Stack;

void initStack(Stack *stack) {
    stack->top = -1;
}

bool isEmpty(Stack *stack) {
    return stack->top == -1;
}

bool isFull(Stack *stack) {
    return stack->top == MAX_CAPACITY - 1;
}

void push(Stack *stack, int value) {
    if (isFull(stack)) {
        printf("Stack Overflow\n");
        return;
    }
    stack->items[++stack->top] = value;
}

// Returns false on underflow instead of a sentinel value, since -1
// (or any int) could otherwise be a legitimate value on the stack.
// The popped value is written to *outValue when the call succeeds.
bool pop(Stack *stack, int *outValue) {
    if (isEmpty(stack)) {
        printf("Stack Underflow\n");
        return false;
    }
    *outValue = stack->items[stack->top--];
    return true;
}

bool peek(Stack *stack, int *outValue) {
    if (isEmpty(stack)) {
        printf("Stack is empty\n");
        return false;
    }
    *outValue = stack->items[stack->top];
    return true;
}

void display(Stack *stack) {
    if (isEmpty(stack)) {
        printf("Empty Stack\n");
        return;
    }
    for (int i = stack->top; i >= 0; i--) {
        printf("%d ", stack->items[i]);
    }
    printf("\n");
}

int main() {
    Stack stack;
    initStack(&stack);

    push(&stack, 10);
    push(&stack, 20);
    push(&stack, 30);
    display(&stack);

    int value;
    if (peek(&stack, &value)) {
        printf("Top element: %d\n", value);
    }

    push(&stack, 40);
    push(&stack, 50);
    push(&stack, 60); // triggers overflow, capacity is 5

    while (pop(&stack, &value)) {
        printf("Popped: %d\n", value);
    }
    pop(&stack, &value); // triggers underflow, stack is now empty

    return 0;
}

Expected output:

30 20 10 
Top element: 30
Stack Overflow
Popped: 50
Popped: 40
Popped: 30
Popped: 20
Popped: 10
Stack Underflow

Both versions follow the same algorithm: initialize top to -1, check isFull() before every push, check isEmpty() before every pop or peek, and move top by exactly one position per operation.

AI-Powered Full Stack Developer ProgramEXPLORE COURSE
Become a Job-Ready Full-Stack Developer

Push Operation in Stack Using Array

Push adds a new element at the next available position when space is available. If top equals capacity - 1, the array is full, which is exactly what isFull() checks for. When space is available:

  1. Confirm the array has remaining capacity.
  2. Increase top by exactly one.
  3. Insert the new value at the updated index.
public void push(int value) {
    if (isFull()) {
        throw new IllegalStateException("Stack Overflow");
    }
    stackArray[++top] = value;
}

Pop Operation in Stack Using Array

Pop retrieves and removes the most recently added element, and the stack's size shrinks by 1. It checks isEmpty() first, since removing from an empty stack (reading index -1) is invalid. A successful pop:

  1. Confirms top is 0 or higher.
  2. Reads the value at the current top index.
  3. Decreases top by exactly one.
  4. Returns the value to the caller.
public int pop() {
    if (isEmpty()) {
        throw new IllegalStateException("Stack Underflow");
    }
    return stackArray[top--];
}
Learn 45+ in-demand full-stack development skills and tools, including Frontend Development, Backend Development, Version Control and Collaboration, Database Management, and AI-Assisted Development, with our AI-Powered Full Stack Developer Course.

Push and Pop in Action: A Step-by-Step Example

Here's what happens to a stack with capacity 5 as a sequence of operations runs:

Step

Operation

Array Contents (bottom to top)

Top Index

Notes

1

Initialize

[ ]

-1

Stack starts empty

2

push(10)

[10]

0

First element added

3

push(20)

[10, 20]

1

4

push(30)

[10, 20, 30]

2

5

peek()

[10, 20, 30]

2

Returns 30, array unchanged

6

pop()

[10, 20]

1

Returns 30, top decremented by one

Every push increases top by exactly one and writes to that new position. Every pop reads the value at the current top index first, then decreases it. Peek behaves like the read half of pop, but never touches top.

Peek and Display Operations

Peek returns the top element without removing it, useful for cases like expression parsers that need to check an operator before deciding what to do with it.

public int peek() {
    if (isEmpty()) {
        throw new IllegalStateException("Stack is empty");
    }
    return stackArray[top];
}

Display is mainly useful for debugging, walking from the current top index down to index 0:

  1. Start at the current top index.
  2. Print the value at that position.
  3. Move down one index at a time.
  4. Stop after printing the element at index 0.

Because display examines every occupied slot, it runs in O(n) time, unlike the O(1) core operations.

With Our Trending Applied Agentic AI CourseExplore Course
Learn to Build Cutting-edge Agentic AI Products

Time and Space Complexity Analysis

Array-based stack operations are fast and predictable because they access memory directly by index rather than searching or traversing.

Operation

Time Complexity

Auxiliary Space

Push

O(1)

O(1)

Pop

O(1)

O(1)

Peek

O(1)

O(1)

isEmpty / isFull

O(1)

O(1)

Display

O(n)

O(1)

The auxiliary space column is the extra temporary memory an operation uses beyond the stack itself, which is constant for all of these. The array's own storage is separate: an array built for 1,000 integers occupies that space as soon as it's created, regardless of how many elements are actually pushed, so total stack storage is O(capacity).

The number of steps push, pop, and peek take stays the same whether the array holds 10 items or 10,000.

Also read: Time and Space Complexities in Data Structures

Advantages and Limitations of Array-Based Stack

Array-based stacks are a strong choice for performance-sensitive, bounded-size use cases:

  • Elements sit in contiguous memory, making sequential reads fast.
  • Push, pop, and peek run in constant time.
  • No pointer overhead, unlike a linked-list stack.
  • Contiguous storage tends to benefit from CPU cache locality.

The fixed size also brings real limitations:

  • A full array triggers overflow, since it can't grow past its declared capacity.
  • An oversized array wastes memory if actual usage stays low.
  • Growing a dynamic array requires occasionally copying all elements to a larger array, an O(n) operation, though this happens infrequently enough that insertion is still amortized O(1) on average.
  • Inserting at the bottom isn't a standard stack operation; it would require shifting every other element, unlike push and pop, which only touch the top.

Applications of a Stack

Stacks show up throughout software, not just in interview questions:

  • Function-call management: the call stack tracks active function calls and their local variables.
  • Undo and redo: each action pushes onto a stack so it can be reversed in order.
  • Browser history: the back button pops the most recently visited page.
  • Expression evaluation: converting and evaluating infix, postfix, and prefix expressions.
  • Parentheses matching: checking that brackets, parentheses, and braces are balanced in code or text.
  • Depth-first search: traversing a graph or tree by exploring as far as possible before backtracking.
  • Backtracking algorithms: solving mazes, puzzles like Sudoku, and similar problems by trying a path and reverting when it fails.
  • Reversing strings or lists: pushing every character or element, then popping them back out in reverse order.

Java Certification TrainingENROLL NOW
Master Core Java 8 Concepts, Java Servlet, & More!

Array vs Linked List Stack

Both approaches solve the same problem with different memory mechanics.

Parameter

Array-Based Stack

Linked List Stack

Memory layout

Fixed, indexed storage

Dynamically allocated nodes

Capacity

Predetermined, fixed

Grows until memory runs out

Overflow condition

Can overflow once all slots are full

Fails only when memory is exhausted

Push/pop speed

Both O(1); contiguous storage can benefit from cache locality, giving arrays a possible edge in practice

O(1), but each new node adds allocation overhead

Memory overhead

No pointer overhead

Extra memory for node pointers

Best suited for

Bounded use cases with a known maximum size

Unpredictable or frequently changing stack sizes

Main limitation

Fixed size can overflow or waste space

Pointer storage and allocation reduce efficiency

In short: use an array-based stack when the maximum size is predictable and raw speed matters most. Use a linked-list stack when the size is unpredictable, and flexibility matters more than a small, hardware-dependent speed edge.

Conclusion

Learning data structures like the stack builds the foundation for writing efficient, well-organized software and thinking about where that leads next. Explore Simplilearn's Software Engineer Roadmap for a practical view of the skills, tools, and roles ahead.

Key Takeaways

  • A stack processes data in strict Last-In, First-Out order.
  • Elements are stored sequentially in a fixed-size array.
  • The top variable, checked by isEmpty() and isFull(), controls all insertion and removal.
  • Push, pop, and peek all run in constant time.
  • The same push, pop, and peek logic carries over from Java to C and other languages; only the syntax for structuring the stack changes.

FAQs

1. What are the basic operations performed on a stack using an array?

The core operations are push (add to the top), pop (remove from the top), and peek (read the top element without removing it). Two supporting checks, isEmpty() and isFull(), run internally before most operations, and a display or traversal operation is commonly added for debugging.

2. What's the difference between stack overflow and stack underflow?

Overflow happens when you try to push onto a stack that's already at capacity. Underflow happens when you try to pop or peek from an empty stack. Both are checked with isFull() and isEmpty() before the operation runs.

3. Why does an array-based stack have a fixed size?

Because the array reserves memory when it's created. If you need a stack that grows and shrinks freely, a linked-list-based stack or a dynamically resizing array is a better fit.

4. What's a real-world use case for a stack?

Browser back-button navigation is a common one: every page you visit gets pushed onto a stack, and hitting back pops the most recent one off.

About the Author

Kusum SainiKusum Saini

Kusum Saini is the Director - Principal Architect at Simplilearn. She has over 12 years of IT experience, including 3.5 years in the US. She specializes in growth hacking and technical design and excels in n-layer web application development using PHP, Node.js, AngularJS, and AWS technologies.

View More
  • Acknowledgement
  • PMP, PMI, PMBOK, CAPM, PgMP, PfMP, ACP, PBA, RMP, SP, OPM3 and the PMI ATP seal are the registered marks of the Project Management Institute, Inc.
  • *All trademarks are the property of their respective owners and their inclusion does not imply endorsement or affiliation.
  • Career Impact Results vary based on experience and numerous factors.