Implement Stack Using an Array

Learn Implement Stack Using an Array with practical examples, explanations, best practices, and real-world coding techniques.

Implementing a Stack with an Array

An array is one of the simplest ways to implement a stack. Since a stack follows the Last In, First Out (LIFO) principle, the end of an array can be used as the top of the stack. Elements are added with push() and removed with pop().

This approach is especially convenient in JavaScript because arrays already provide the operations needed to build a basic stack. A custom class can then expose only the operations that a stack should support.

How the Array Represents a Stack

Consider an array containing three values:

const stack = [10, 20, 30];

                 Top
                  ↓
[10, 20, 30]

The last array element represents the top of the stack. Therefore:

  • stack.push(40) adds 40 to the top.
  • stack.pop() removes 40 from the top.
  • stack[stack.length - 1] reads the top value.
  • stack.length gives the current stack size.

Basic Array-Based Stack

The simplest implementation can use an array directly:

const stack = [];

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

console.log(stack);
// [10, 20, 30]

console.log(stack.pop());
// 30

console.log(stack);
// [10, 20]

Although this works, exposing the array directly allows other code to perform operations that do not follow stack behavior. A class provides a cleaner abstraction.

Creating a Stack Class

A custom stack class can keep the underlying array private from the code using the data structure conceptually and provide methods such as push, pop, peek, isEmpty, and size.

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;
  }
}

Adding Elements with Push

The push operation adds a new value to the end of the array. Because the end represents the top of the stack, the new value immediately becomes the top element.

const stack = [];

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

console.log(stack);
// ["A", "B", "C"]

After these operations, C is the top element. If another value is pushed, it will be placed above C.

Removing Elements with Pop

The pop operation removes the last element of the array and returns it. This matches the LIFO behavior of a stack.

const stack = ["A", "B", "C"];

const removed = stack.pop();

console.log(removed);
// C

console.log(stack);
// ["A", "B"]

The important point is that pop() removes only the current top element. The remaining elements stay in their original order.

Reading the Top with Peek

JavaScript arrays do not provide a method specifically named peek(), so we can access the last element using its index.

const stack = [10, 20, 30];

const top = stack[stack.length - 1];

console.log(top);
// 30

console.log(stack);
// [10, 20, 30]

The array is unchanged because reading the last element does not remove it. A custom stack class can wrap this expression inside a peek() method.

Checking Whether the Stack Is Empty

The stack is empty when its array contains zero elements. The length property makes this check straightforward.

const stack = [];

console.log(stack.length === 0);
// true

stack.push(100);

console.log(stack.length === 0);
// false

Encapsulating this logic inside an isEmpty() method makes algorithms easier to read and keeps implementation details out of calling code.

Getting the Stack Size

Since the array stores every stack element, its length property represents the current stack size.

const stack = [15, 25, 35, 45];

console.log(stack.length);
// 4

No additional counter is necessary when using the array's length directly.

Complete Array-Based Implementation

The following implementation brings the core operations together into a reusable stack data structure.

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

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

  pop() {
    if (this.items.length === 0) {
      return undefined;
    }

    return this.items.pop();
  }

  peek() {
    if (this.items.length === 0) {
      return undefined;
    }

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

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

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

  clear() {
    this.items.length = 0;
  }
}

const stack = new Stack();

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

console.log("Top:", stack.peek());
console.log("Size:", stack.size());

console.log("Removed:", stack.pop());

console.log("Top:", stack.peek());
console.log("Size:", stack.size());

Understanding the Operation Flow

Suppose an empty stack receives the following operations: push(10), push(20), push(30), and then pop().

Step Operation Array Top
1 push(10) [10] 10
2 push(20) [10, 20] 20
3 push(30) [10, 20, 30] 30
4 pop() [10, 20] 20

Why the End of the Array Should Be the Top

A common implementation mistake is to use the beginning of the array as the top and call unshift() and shift(). While this can model the correct LIFO behavior, removing or adding at the beginning can require existing elements to be moved.

// Less suitable for a large stack

const stack = [];

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

const value = stack.shift();

Using the end of the array avoids this unnecessary shifting in the normal case:

// Preferred approach

const stack = [];

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

const value = stack.pop();

Handling an Empty Array

Calling pop() on an empty JavaScript array returns undefined. A custom stack can preserve this behavior or explicitly check the stack before removing an element.

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

  pop() {
    if (this.items.length === 0) {
      return undefined;
    }

    return this.items.pop();
  }

  peek() {
    if (this.items.length === 0) {
      return undefined;
    }

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

const stack = new Stack();

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

Array Stack and Memory

A stack implemented with a dynamic JavaScript array can grow as elements are added. JavaScript manages the underlying array storage automatically. This makes the approach convenient, but the runtime may occasionally allocate a larger internal storage area as the array grows.

For most application-level stack usage, this implementation detail does not need to be managed manually. When learning data structures, the important design decision is to keep the stack's top at the array end so the primary operations remain efficient.

Array Stack vs Linked List Stack

Feature Array Stack Linked List Stack
Storage Array elements Individual nodes
Push O(1) amortized O(1)
Pop O(1) O(1)
Memory overhead Generally lower per element Extra reference per node
Implementation Simple More manual pointer management

When an Array-Based Stack Is Useful

An array-based stack is a practical choice when the application needs a straightforward LIFO structure and does not require specialized linked-node behavior. Common examples include:

  • Undo operations in editors
  • Temporary processing of tasks in reverse order
  • Depth-first search implementations
  • Backtracking algorithms
  • Expression and parentheses processing
  • Maintaining application state history
  • Interview and competitive programming problems

Common Implementation Mistakes

  • Using shift() and unshift() for a large stack when the array end can be used instead.
  • Forgetting to handle pop and peek when the stack is empty.
  • Exposing the underlying array and allowing arbitrary insertion or deletion that breaks stack behavior.
  • Confusing the top element with the first element of the array.
  • Assuming that every array operation has the same complexity as push and pop.

Complexity of an Array-Based Stack

When the end of the array represents the stack top, the main stack operations are efficient.

Operation Time Complexity
push O(1) amortized
pop O(1)
peek O(1)
isEmpty O(1)
size O(1)

The amortized O(1) complexity for push accounts for occasional internal resizing performed by a dynamic array implementation. Individual resizing operations can take longer, but over a sequence of insertions the average cost remains constant under the usual dynamic-array growth strategy.

Key Takeaways

  • An array can represent a stack by using its end as the top.
  • push() adds an element to the top.
  • pop() removes the most recently added element.
  • peek() reads the top without removing it.
  • The array's length provides the stack size.
  • Using the array end avoids the shifting associated with front-based operations.
  • Push is typically O(1) amortized, while pop and peek are O(1).
  • Array-based stacks are simple and practical for many LIFO-based problems.

Code Examples

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

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

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

    return this.items.pop();
  }

  peek() {
    return this.isEmpty()
      ? undefined
      : 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
    
const stack = [];
const values = [10, 20, 30, 40];

for (const value of values) {
  stack.push(value);
}

const reversed = [];

while (stack.length > 0) {
  reversed.push(stack.pop());
}

console.log(reversed);
// [40, 30, 20, 10]
    
class Stack {
  constructor() {
    this.items = [];
  }

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

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

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

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

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

  clear() {
    this.items.length = 0;
  }
}

const stack = new Stack();

stack.push("HTML");
stack.push("CSS");
stack.push("JavaScript");

console.log(stack.peek()); // JavaScript

stack.clear();

console.log(stack.isEmpty()); // true
console.log(stack.size());    // 0
    

Pro Tip

When implementing a stack with a JavaScript array, treat the last array position as the top. Using push() and pop() at the same end keeps the implementation simple and avoids the unnecessary element shifting associated with shift() and unshift().

🎨 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.