Skip to content
Shafin Chowdhury
Go back

Stack Using Vector in C++: Step-by-Step Class Implementation

Software Developer & Computer Science Student

Published:
8 min read
Edit page
Table of Contents13 topics

In this tutorial, you will learn how a C++ vector works under the hood, why its back end maps naturally to the Top of a Stack, and how to write a complete, reusable Stack class using vector.

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


1. What Is a Vector in C++?

A vector (available in the <vector> header) is a dynamic array provided by the C++ Standard Template Library (STL):

#include <vector>

class Stack {
private:
    vector<int> elements; // Dynamic array that grows and shrinks automatically
};

Like a normal C++ array, a vector stores elements side by side in contiguous memory and lets you access any element by index (elements[0], elements[1], etc.). However, unlike a fixed-size array whose size must be locked in at compile time, a vector automatically grows and shrinks at runtime as you add or remove elements.


2. Why Can We Use a Vector to Implement a Stack?

Recall what a Stack requires:

  • We must insert new elements at one end (Top).
  • We must remove elements from that same end (Top).
  • We must inspect the newest element at that end (Top).

A vector already provides three built-in O(1)O(1) operations at its back end (the highest index):

elements.push_back(40);      // 1. Insert 40 at the back (Top of Stack)
int topVal = elements.back();// 2. Read the back element (40) without removing it
elements.pop_back();         // 3. Remove the back element (40)

By treating the back of the vector (index = size - 1) as the Top of our Stack, every Stack operation maps directly to a built-in vector function!

Vector Indices:     [0]     [1]     [2]     [3]
                  +-----+ +-----+ +-----+ +-----+
Vector Elements:  |  10 | |  20 | |  30 | |  40 | <-- Back of Vector = TOP of Stack
                  +-----+ +-----+ +-----+ +-----+
                     ^                       ^
                   Bottom                   TOP
                (First In)               (Last In)

Why not use the front (index 0) as the Top? If you insert or delete at index 0 of a vector, every other element must shift one slot to the right or left, which takes slow O(n)O(n) time. Operating at the back requires zero shifting!


3. Mapping Stack Operations to Vector Functions

Before writing the class, let’s see how each Stack operation translates into a std::vector member function:

Stack OperationVector Function UsedWhat It Does
push(value)elements.push_back(value)Appends value to the end of the vector.
pop()elements.pop_back()Removes the last element from the vector (does not return it).
top()elements.back()Returns the last element in the vector without removing it.
empty()elements.empty()Returns true if the vector has 0 elements, otherwise false.
size()elements.size()Returns the total number of elements currently in the vector.

4. Implementing Each Operation Step by Step (With Visual Traces)

Let’s examine each operation individually alongside its C++ code and visual memory trace. Assume our Stack class has the private member variable:

vector<int> elements;

1. push(int value) — Inserting an Element

// Push an element onto the top of the stack
void push(int value) {
    elements.push_back(value);
    cout << value << " pushed onto the stack." << endl;
}
  • elements.push_back(value);: Places value at the very end of the vector. Since the end of the vector represents the Top of our stack, value becomes the new top element.

Here is the visual trace when we call push(10), push(20), and push(30) on an empty stack:

1. Initial State (empty):
   elements: [ ]
   size = 0

2. After push(10) -> elements.push_back(10):
   Index:     0
            +----+
   elements:| 10 |
            +----+
              ^
             TOP (elements.back() == 10)

3. After push(20) -> elements.push_back(20):
   Index:     0    1
            +----+----+
   elements:| 10 | 20 |
            +----+----+
                   ^
                  TOP (elements.back() == 20)

4. After push(30) -> elements.push_back(30):
   Index:     0    1    2
            +----+----+----+
   elements:| 10 | 20 | 30 |
            +----+----+----+
                        ^
                       TOP (elements.back() == 30)

2. pop() — Removing the Top Element

// Remove the top element from the stack
void pop() {
    if (elements.empty()) {
        cout << "Stack Underflow! Cannot pop from an empty stack." << endl;
        return;
    }
    int removedValue = elements.back();
    elements.pop_back();
    cout << removedValue << " popped from the stack." << endl;
}
  • if (elements.empty()): Always check whether the stack is empty before removing anything. Calling pop_back() on an empty vector causes undefined behavior (a crash).
  • int removedValue = elements.back();: Important detail: elements.pop_back() removes the last element from the vector, but its return type is void—it does not return the removed value! If we want to print or use the value being removed, we must first read it using elements.back().
  • elements.pop_back();: Shrinks the vector’s size by 1, effectively removing the top element.

Here is the visual trace when we call pop() on [10, 20, 30]:

Before pop():
   Index:     0    1    2
            +----+----+----+
   elements:| 10 | 20 | 30 |
            +----+----+----+
                        ^
                       TOP (size = 3)

After pop() -> elements.pop_back():
   Index:     0    1
            +----+----+
   elements:| 10 | 20 |        (30 is removed from the back)
            +----+----+
                   ^
                  TOP (elements.back() == 20, size = 2)

3. top() — Reading the Top Element

// Return the top element without removing it
int top() {
    if (elements.empty()) {
        cout << "Stack is empty! No top element exists." << endl;
        return -1;
    }
    return elements.back();
}
  • elements.back(): Accesses and returns the last element of the vector (at index elements.size() - 1) without deleting it.
  • If the stack is empty, we print a clear warning message and return -1 as a sentinel value.

4. empty() — Checking if the Stack Is Empty

// Check if the stack is empty
bool empty() {
    return elements.empty();
}
  • elements.empty(): Returns true when the vector contains zero elements, and false otherwise.

5. size() — Counting the Elements

// Return the number of elements in the stack
int size() {
    return elements.size();
}
  • elements.size(): Returns the current number of elements stored in the vector.

6. display() — Printing from Top to Bottom

// Display all elements from top to bottom
void display() {
    if (elements.empty()) {
        cout << "Stack is empty." << endl;
        return;
    }
    cout << "Stack elements (Top to Bottom): ";
    for (int i = elements.size() - 1; i >= 0; i--) {
        cout << elements[i] << " ";
    }
    cout << endl;
}
  • Because the newest element is at elements.size() - 1 and the oldest element is at index 0, looping i backwards from elements.size() - 1 down to 0 prints the stack from Top to Bottom.

5. Complete Class-Based Implementation in C++

Here is the complete, compilable C++ program implementing a Stack class using std::vector, along with a main() function that tests every operation:

#include <iostream>
#include <vector>

using namespace std;

class Stack {
private:
    vector<int> elements;

public:
    // Push an element onto the top of the stack
    void push(int value) {
        elements.push_back(value);
        cout << value << " pushed onto the stack." << endl;
    }

    // Remove the top element from the stack
    void pop() {
        if (elements.empty()) {
            cout << "Stack Underflow! Cannot pop from an empty stack." << endl;
            return;
        }
        int removedValue = elements.back();
        elements.pop_back();
        cout << removedValue << " popped from the stack." << endl;
    }

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

    // Check if the stack is empty
    bool empty() {
        return elements.empty();
    }

    // Return the number of elements in the stack
    int size() {
        return elements.size();
    }

    // Display all elements from top to bottom
    void display() {
        if (elements.empty()) {
            cout << "Stack is empty." << endl;
            return;
        }
        cout << "Stack elements (Top to Bottom): ";
        for (int i = elements.size() - 1; i >= 0; i--) {
            cout << elements[i] << " ";
        }
        cout << endl;
    }
};

int main() {
    Stack myStack;

    // Push elements
    myStack.push(10);
    myStack.push(20);
    myStack.push(30);

    // Display current state
    myStack.display();
    cout << "Current Top element: " << myStack.top() << endl;
    cout << "Current Stack size: " << myStack.size() << endl;

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

    // Pop elements
    myStack.pop();
    cout << "Top element after one pop: " << myStack.top() << endl;
    myStack.display();

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

    // Pop remaining elements and test underflow protection
    myStack.pop();
    myStack.pop();
    myStack.pop(); // Attempting to pop from an empty stack

    if (myStack.empty()) {
        cout << "Confirmed: Stack is now empty." << endl;
    }

    return 0;
}

Output

10 pushed onto the stack.
20 pushed onto the stack.
30 pushed onto the stack.
Stack elements (Top to Bottom): 30 20 10 
Current Top element: 30
Current Stack size: 3
---
30 popped from the stack.
Top element after one pop: 20
Stack elements (Top to Bottom): 20 10 
---
20 popped from the stack.
10 popped from the stack.
Stack Underflow! Cannot pop from an empty stack.
Confirmed: Stack is now empty.

How the display() Function Works

Look closely at the for loop inside display():

for (int i = elements.size() - 1; i >= 0; i--)

Because the newest element is at the end of the vector (elements.size() - 1) and the oldest element is at index 0, counting backwards from elements.size() - 1 down to 0 prints the stack from Top to Bottom.


6. STL std::stack vs. Manually Implemented Vector Stack

You might wonder: “If std::stack already exists in STL, why build a Stack with std::vector?”

FeatureSTL std::stack<T>Manual Vector-Based Stack
Default Underlying Containerstd::deque<T> (can also be configured as std::stack<int, vector<int>>)std::vector<T>
Inspecting All Elements (display)Not allowed (strictly hides everything except .top())Easy! You can loop through the internal vector for debugging or printing.
Underflow SafetyCalling .pop() on an empty std::stack crashes with undefined behavior.You can add beginner-friendly if (empty()) checks and error messages.
Memory LayoutChunked blocks (deque) by defaultSingle contiguous block of memory (great CPU cache locality).

In fact, you can even tell std::stack to use std::vector internally by writing:

stack<int, vector<int>> customStlStack;

Building the class yourself removes the magic and proves that a container adapter is simply a clean wrapper around an existing data structure!


7. Time and Space Complexity

OperationTime ComplexityExplanation
push(val)O(1)O(1) AmortizedInstantaneous almost every time. Occasionally takes O(n)O(n) when the vector resizes, averaging out to O(1)O(1) per insertion.
pop()O(1)O(1)Decrements the internal vector size by 1 without moving any other elements.
top()O(1)O(1)Directly reads elements[size - 1].
empty()O(1)O(1)Checks whether size == 0.
size()O(1)O(1)Returns the vector’s stored size counter.
  • Space Complexity: O(n)O(n), where nn is the number of elements in the stack.

8. Advantages and Disadvantages

Advantages

  • No Fixed Size Limit: Unlike a fixed-size array, a vector grows automatically as long as RAM is available.
  • Automatic Memory Management: You do not need to write new or delete—std::vector cleans up its own heap memory automatically when the Stack object goes out of scope (following RAII principles).
  • Cache-Friendly Performance: Because all elements sit next to each other in contiguous memory, the CPU cache reads them much faster than scattered linked-list nodes.

Disadvantages

  • Occasional Reallocation Pause: When the vector needs more space, allocating a larger block and copying elements causes a brief O(n)O(n) pause.
  • Unused Capacity Overhead: Reserved capacity may briefly exceed the exact number of elements currently stored.

9. Frequently Asked Questions (FAQ)

What is the difference between vector::back() and vector::pop_back()?

vector::back() returns a reference to the last element without removing it (equivalent to top()). vector::pop_back() removes the last element from the vector and returns void (nothing).

Does elements.pop_back() shrink the vector’s allocated memory capacity?

No. Calling pop_back() reduces the vector’s size by 1, but its capacity stays the same so future push_back() calls can reuse that memory without reallocating.

Why is push_back() called “amortized” O(1)O(1)?

Suppose you push 1,0001{,}000 items. Almost all pushes take 1 step, while resizing happens only at powers of two (1,2,4,8,16,…,5121, 2, 4, 8, 16, \dots, 512). The total work across nn pushes is roughly 2n2n operations, which averages out to a constant O(1)O(1) cost per push.


Summary

  • A std::vector makes it easy to implement a dynamic Stack in C++.
  • The back of the vector acts as the Top of the stack.
  • Use push_back(x) for push(x), pop_back() for pop(), and back() for top().
  • Remember that pop_back() does not return the removed element—use back() first if you need its value.
  • All core stack operations run in O(1)O(1) time (amortized for push).

Continue Learning in the Stack Series

Now that you know how to build a Stack using std::vector, what if you are in a university exam or embedded environment where STL containers like vector are not allowed? Next, we will build a Stack from raw memory using a fixed-size C++ array!


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
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 Array: Fixed-Size Implementation, Overflow & Dry Run
Next Post
Stack in C++: Complete Beginner's Guide