Skip to main content

Command Palette

Search for a command to run...

Understanding Stack Data Structure in JavaScript (With Real Examples)

Updated
6 min read
Understanding Stack Data Structure in JavaScript (With Real Examples)

What is a Stack?

A stack is a data structure where the last thing you put in is the first thing you take out.

Key Rule:

LIFO (Last In, First Out)

Real-Life Examples of Stack

Undo / Redo in a Text Editor

We type A B C in text editor, A, B , C all are pushed in a stack 1 ( we have to stacks one for undo one for redo, whatever we type goes in stack 1 and initially stack 2 is empty.

Then we press ctrl + Z, Now C is gone, where ? in stack 2 so that if we need to do Redo we should be able to take that value from stack 2 and put back in stack 1.

Makes sense?

A stack is ultimately a list, but with strict access rules.

Rules:

  • Elements can be added only from one end (top)

  • Elements can be removed only from that same end

  • No access to middle or bottom elements

Defining a Stack in JavaScript

To create a stack, we first need a container that will hold our data.

In JavaScript, the simplest and most efficient choice is an array.

So we start by defining a Stack class.

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

What is happening here?

  • We create a class called Stack

  • Inside the constructor, we initialize an empty array

  • This array will store all stack elements

  • The end of the array represents the top of the stack

At this point, we have a stack structure, but it can’t do anything yet.

Adding Data to the Stack (push)

To add elements to a stack, we use the push operation.

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

  // Stack only grows from one end. This is how new data enters.
  push(data){
    this.stack.push(data)
  }
// Mental Model: I am doing something new - save it for possible undo later.
}

What this does:

  • Takes data as input

  • Adds it to the top of the stack

  • Internally, Array.push() adds the element to the end of the array

This follows stack rules because:

  • We are adding elements only from one end

  • No middle or bottom insertion is allowed

Removing Data from the Stack (pop)

To remove the most recent element, we use pop.

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

  push(data){
    this.stack.push(data)
  }

  pop(){
    this.stack.pop()
  }
// Mental Trigger: Take back the most recent thing.
}

What this does:

  • Removes the top element of the stack

  • Follows LIFO (Last In, First Out)

  • Uses JavaScript’s Array.pop() internally

This is how undo, backtracking, and function calls work.

Viewing the Top Element (peek)

Sometimes we only want to see the top element without removing it.

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

  push(data){
    this.stack.push(data)
  }

  pop(){
    this.stack.pop()
  }

// Why it exist : something we neede to look before we act.
  peek(){
    return this.stack[this.stack.length - 1]
  }
}

What this does:

  • Accesses the last element of the array

  • Returns it without modifying the stack

This is useful when:

  • You want to know what will be popped next

  • You need to validate something before removing it

Checking if the Stack Is Empty (isEmpty)

Before popping, it’s important to know whether the stack has elements or not.

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

  push(data){
    this.stack.push(data)
  }

  pop(){
    this.stack.pop()
  }

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

// why it exits : popping from an empty stack = bug/ crash/ undefiend behaviour
  isEmpty(){
    return this.stack.length === 0
  }
}

What this does:

  • Returns true if the stack has no elements

  • Returns false otherwise

This prevents:

  • Errors

  • Unexpected behavior

  • Crashes from popping an empty stack

Stack Utility Methods: size, clear, contains, and reverse

After implementing the core stack operations, we usually need a few helper methods to make the stack easier to work with.

These methods don’t change how a stack behaves, but they help us inspect, reset, or validate the stack.

Below is the relevant part of the stack implementation:

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

  push(data){
    this.stack.push(data)
  }

  pop(){
    this.stack.pop()
  }

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

  isEmpty(){
    return this.stack.length === 0
  }

// Returns the number of elements in the stack
  size(){
    return this.stack.length
  }

// Clears the entire stack: Yes its that simple 
  clear(){
    this.stack = []
  }

// Checks if a value exists anywhere in the stack
  contains(element){
    return this.stack.includes(element)
  }

// Optional: It reverses 
  reverse(){
    this.stack.reverse()
  }

}

size()

The size() method returns the total number of elements currently present in the stack.

It simply returns the length of the underlying array and does not modify the stack in any way.

This is useful for debugging, validations, or when you need to know how full the stack is.


clear()

The clear() method removes all elements from the stack.

Instead of popping elements one by one, we just assign a new empty array.

Sometimes the easiest solution really is to start fresh.

This is commonly used when:

  • Resetting application state

  • Clearing undo history

Reinitializing the stack


contains(element)

The contains() method checks whether a given element exists anywhere in the stack.

While this is useful, it’s worth noting that this method breaks pure stack abstraction, since a stack is supposed to expose only the top element.

That said, it’s perfectly fine as a utility method for learning, debugging, or validation.


Using the Stack (Final Example)

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

  // To add data in stack
  push(data){
    this.stack.push(data)
  }

  pop(){
    this.stack.pop()
  }

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

  isEmpty(){
    return this.stack.length === 0
  }

  size(){
    return this.stack.length
  }

  clear(){
    this.stack = []
  }

  contains(element){
    return this.stack.includes(element)
  }

  reverse(){
    this.stack.reverse()
  }

  printStack(){
    let str = ""
    for (let i = 0; i < this.stack.length; i++) {
      str += this.stack[i] + "\n"
    }
    return str
  }
}

const myStack = new Stack()

myStack.push(8)
myStack.push(3)
myStack.push(4)
myStack.push(3)

console.log("This is the Element at Top: ".myStack.peek())
console.log("Now printing the stack values: ")
console.log(myStack.printStack())
asmit~$node stack/index.js
This is the Element at Top:  3
Now printing the stack values: 
8
3
4
3

This shows:

  • peek() returns the most recently added element

  • printStack() displays the stack from bottom to top

  • Stack behavior follows Last In, First Out


Final Thoughts

A stack is simple in structure but extremely powerful in practice.

It is used in:

  • Undo / Redo systems

  • Function call handling

  • Expression evaluation

  • Backtracking problems

Once you understand stacks clearly, learning Queue, Linked List, and Recursion becomes much easier.

This implementation is intentionally kept simple to focus on understanding, not overengineering.


What’s Next?

In the next article, we’ll look at the Queue data structure and see how changing just one rule completely changes behavior.


Key Takeaway

A stack is just a list with discipline.