Stack Operations: Push, Pop and Peek

Learn Stack Operations: Push, Pop and Peek with practical examples, explanations, best practices, and real-world coding techniques.

Understanding Stack Operations

A stack is a linear data structure that follows the Last In, First Out (LIFO) principle. The element added most recently is the first element removed. Because insertion and removal happen at the same end, a stack can provide predictable and efficient access to the current top element.

The main stack operations are push, pop, and peek. Supporting operations such as isEmpty and size help determine the current state of the stack. These operations are used in algorithms, recursion, browser history, undo systems, expression processing, and many coding interview problems.

The Top of a Stack

A stack has a single logical access point called the top. New elements are added to the top, and elements are also removed from the top. For example, consider this stack:

Top → 40
      30
      20
      10

If we perform pop(), the value 40 is removed first. The value 30 then becomes the new top element.

1. Push Operation

The push operation adds a new element to the top of the stack. Every successful push increases the number of elements by one.

Stack: [10, 20, 30]

push(40)

Stack: [10, 20, 30, 40]
                         ↑
                        Top

In an array-based implementation, JavaScript's push() method can be used directly. A linked-list implementation instead creates a new node and places it at the top.

2. Pop Operation

The pop operation removes and returns the element currently at the top of the stack.

Stack: [10, 20, 30, 40]

pop()

Removed: 40

Stack: [10, 20, 30]

A pop operation should normally be performed only when the stack contains an element. Attempting to remove an element from an empty stack is called stack underflow.

3. Peek Operation

The peek operation returns the top element without removing it from the stack. It is useful when an algorithm needs to inspect the next value that would be removed.

Stack: [10, 20, 30]

peek()

Result: 30

Stack remains: [10, 20, 30]

This makes peek different from pop. pop() changes the stack, while peek() only reads its current top.

4. isEmpty Operation

The isEmpty operation checks whether the stack contains any elements. It usually returns a Boolean value.

Stack: []

isEmpty() → true

Stack: [10, 20]

isEmpty() → false

Checking whether a stack is empty before calling pop or peek is an important defensive programming practice, especially when implementing a stack manually.

5. Size Operation

The size operation returns the number of elements currently stored in the stack.

Stack: [10, 20, 30]

size() → 3

In a JavaScript array implementation, the built-in length property can provide the size directly. A custom implementation can also maintain a separate counter.

A Complete Sequence of Stack Operations

Stack operations become easier to understand when they are viewed as a sequence. Consider an initially empty stack:

Operation Result Stack
push(10) 10 added [10]
push(20) 20 added [10, 20]
peek() 20 returned [10, 20]
push(30) 30 added [10, 20, 30]
pop() 30 removed [10, 20]
size() 2 returned [10, 20]

Implementing Stack Operations with an Array

An array is one of the simplest ways to implement a stack in JavaScript. The end of the array can represent the top of the stack. This allows push() and pop() to operate efficiently.

class Stack {
  constructor() {
    this.items = [];
  }

  push(value) {
    this.items.push(value);
  }

  pop() {
    if (this.isEmpty()) {
      return undefined;
    }

    return this.items.pop();
  }

  peek() {
    if (this.isEmpty()) {
      return undefined;
    }

    return this.items[this.items.length - 1];
  }

  isEmpty() {
    return this.items.length === 0;
  }

  size() {
    return this.items.length;
  }
}

The implementation keeps the stack rules in one place. Consumers of the class do not need to manipulate the underlying array directly.

Using the Stack

const stack = new Stack();

stack.push("A");
stack.push("B");
stack.push("C");

console.log(stack.peek());  // C
console.log(stack.size());  // 3

console.log(stack.pop());   // C
console.log(stack.pop());   // B

console.log(stack.peek());  // A
console.log(stack.size());  // 1

Notice that the values are removed in the reverse order from which they were inserted. This is the defining behavior of a stack.

Handling an Empty Stack

A stack implementation must define what happens when pop() or peek() is called while the stack is empty. One common approach in JavaScript is to return undefined. Another approach is to throw an error when an empty operation represents invalid program state.

const stack = new Stack();

console.log(stack.pop());   // undefined
console.log(stack.peek());  // undefined
console.log(stack.isEmpty()); // true

The appropriate behavior depends on the application. For reusable data structures, clearly documenting empty-stack behavior helps prevent unexpected results.

Time Complexity of Stack Operations

A well-designed stack provides constant-time access to its primary operations. When an array uses its end as the stack top, JavaScript can perform push and pop efficiently without shifting all existing elements.

Operation Typical Time Purpose
push O(1) amortized Add an element to the top
pop O(1) Remove the top element
peek O(1) Read the top element
isEmpty O(1) Check whether the stack is empty
size O(1) Get the number of elements

The exact complexity can depend on the underlying implementation. For example, using the beginning of a JavaScript array as the stack top and repeatedly calling shift() can require elements to be moved, making the operation less efficient than using the array end.

Stack Operations with a Linked List

A stack can also be implemented with a linked list. The head node represents the top of the stack. Pushing creates a new head node, while popping removes the current head.

class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

class LinkedStack {
  constructor() {
    this.top = null;
    this.count = 0;
  }

  push(value) {
    const node = new Node(value);

    node.next = this.top;
    this.top = node;
    this.count++;
  }

  pop() {
    if (this.top === null) {
      return undefined;
    }

    const value = this.top.value;
    this.top = this.top.next;
    this.count--;

    return value;
  }

  peek() {
    return this.top ? this.top.value : undefined;
  }

  isEmpty() {
    return this.top === null;
  }

  size() {
    return this.count;
  }
}

Both push and pop operate at the head of the linked list, so they can remain constant-time operations. A linked-list stack does not require a contiguous block of memory, but every node requires additional storage for its reference to the next node.

Stack Overflow and Underflow

Two terms frequently appear when studying stack operations: overflow and underflow.

  • Stack overflow: attempting to add an element when a fixed-capacity stack has no available space.
  • Stack underflow: attempting to remove or inspect an element when the stack is empty.

In dynamic JavaScript arrays, capacity management is handled by the runtime, so application-level stack overflow is usually not the same concern as it is with a fixed-size stack. However, the concept remains important when implementing bounded stacks and when studying data structures.

Where These Operations Are Used

The simplicity of stack operations makes them useful building blocks for many systems. Common examples include:

  • Undo and redo functionality
  • Browser navigation history
  • Function calls and the runtime call stack
  • Expression evaluation
  • Balanced parentheses checking
  • Depth-first search and backtracking
  • Parsing and syntax processing
  • Reversing sequences of data

Common Mistakes

  • Removing from the wrong end of the underlying array and accidentally changing the intended complexity.
  • Calling pop or peek without handling an empty stack.
  • Confusing peek with pop and unintentionally modifying the stack.
  • Forgetting to update a size counter in a custom linked-list implementation.
  • Treating a stack like a queue by attempting to remove the oldest element first.

Stack Operations in Interview Problems

Many coding interview questions are based on recognizing when the LIFO behavior of a stack matches the problem. A useful first step is to identify whether the problem repeatedly needs access to the most recently added unresolved item.

Problems involving matching symbols, reversing decisions, nested structures, backtracking, or maintaining previous states are often good candidates for stack-based solutions. Once the appropriate data structure is identified, the core operations push, pop, and peek usually form the foundation of the algorithm.

Key Takeaways

  • A stack follows the Last In, First Out principle.
  • Push adds an element to the top.
  • Pop removes and returns the top element.
  • Peek reads the top element without removing it.
  • isEmpty checks whether the stack contains elements.
  • Size reports the current number of elements.
  • A well-designed stack can perform its core operations in O(1) time.
  • Stacks can be implemented using arrays or linked lists.

Code Examples

class Stack {
  constructor() {
    this.items = [];
  }

  push(value) {
    this.items.push(value);
  }

  pop() {
    return this.items.pop();
  }

  peek() {
    return this.items[this.items.length - 1];
  }

  isEmpty() {
    return this.items.length === 0;
  }

  size() {
    return this.items.length;
  }
}

const stack = new Stack();

stack.push(10);
stack.push(20);
stack.push(30);

console.log(stack.peek());    // 30
console.log(stack.pop());     // 30
console.log(stack.size());    // 2
console.log(stack.isEmpty()); // false
    
const stack = [];

stack.push("Task A");
stack.push("Task B");
stack.push("Task C");

while (stack.length > 0) {
  const currentTask = stack.pop();
  console.log("Processing:", currentTask);
}

// Processing: Task C
// Processing: Task B
// Processing: Task A
    
class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

class Stack {
  constructor() {
    this.top = null;
    this.count = 0;
  }

  push(value) {
    const node = new Node(value);

    node.next = this.top;
    this.top = node;
    this.count++;
  }

  pop() {
    if (this.top === null) {
      return undefined;
    }

    const value = this.top.value;
    this.top = this.top.next;
    this.count--;

    return value;
  }

  peek() {
    return this.top ? this.top.value : undefined;
  }

  size() {
    return this.count;
  }

  isEmpty() {
    return this.top === null;
  }
}

const stack = new Stack();

stack.push("A");
stack.push("B");
stack.push("C");

console.log(stack.pop());   // C
console.log(stack.peek());  // B
console.log(stack.size());  // 2
    

Pro Tip

When implementing a stack with a JavaScript array, use the end of the array as the top. This allows push() and pop() to work efficiently without repeatedly shifting the remaining elements.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.