Implementing a Stack
Build a stack from scratch using an array, then using a linked list. Compare the two implementations and their trade-offs.
What Is It?
Now that you know what a stack does, let's build one. There are two common ways:
- Array-based stack — use a regular array plus a number that tracks where the top is.
- Linked-list-based stack — use a chain of nodes where adding and removing always happens at the front.
Both give you O(1) — instant — push, pop, and peek. The main difference is how they handle memory.
Analogy
Think of two ways to stack books:
Array approach: You have a shelf with 10 numbered slots (0 through 9). You fill slots from left to right. A sticky note on the shelf says "top = 2" so you always know where the current top is. Fast and simple, but you can't fit more than 10 books without getting a bigger shelf.
Linked list approach: You tie each new book to the previous one with a piece of string. You always hold the string attached to the newest book. To add, tie the new book to the current top and switch to holding the new string. To remove, follow the string to the next book. No limit on books, but each book needs its own piece of string (a little extra memory).
How It Works
Implementation 1: Array-Based Stack
The idea is simple. Use an array and an integer called top that starts at -1 (meaning the stack is empty). Each push moves top up by one and stores the value. Each pop reads the value and moves top down by one.
structure ArrayStack:array = new array of size CAPACITYtop = -1
What it looks like in memory:
Capacity = 5Index: [0] [1] [2] [3] [4]Data: [10] [20] [30] [ ] [ ]^top = 2
The top is at index 2 (value 30). Slots 3 and 4 are unused.
#### Full Pseudocode
function push(stack, value):if stack.top equals CAPACITY - 1:error("Stack is full")stack.top = stack.top + 1stack.array[stack.top] = valuefunction pop(stack):if stack.top equals -1:error("Stack is empty")value = stack.array[stack.top]stack.top = stack.top - 1return valuefunction peek(stack):if stack.top equals -1:error("Stack is empty")return stack.array[stack.top]function isEmpty(stack):return stack.top equals -1function size(stack):return stack.top + 1
#### Tracing Through Operations
After popping, the old values still sit in the array — but they're invisible because top has moved below them. They'll be overwritten by future pushes. We never look at anything above top, so it doesn't matter.
#### Time Complexity
Every operation is O(1) — instant. No loops, no searching. Just move an index and read or write one slot.
#### What If the Array Gets Full?
If you push past the capacity, that's a stack overflow. Two solutions:
- Pick a capacity large enough for your use case.
- Use a dynamic array: when the array fills up, create a new one double the size and copy everything over. This makes push O(1) on average — it's occasionally slow (during the copy) but fast almost every time.
Implementation 2: Linked-List-Based Stack
The idea: each item is a node — a small object that holds a value and a pointer to the next node. The top of the stack is always the first node (called the head). Push adds a new node at the front. Pop removes the front node. Both are instant.
structure Node:datanextstructure LinkedStack:head = nullcount = 0
What it looks like:
top (head)|v[30|*]-->[20|*]-->[10|null]After push(40):top (head)|v[40|*]-->[30|*]-->[20|*]-->[10|null]
#### Full Pseudocode
function push(stack, value):newNode = new Node(value)newNode.next = stack.headstack.head = newNodestack.count = stack.count + 1function pop(stack):if stack.head is null:error("Stack is empty")value = stack.head.datastack.head = stack.head.nextstack.count = stack.count - 1return valuefunction peek(stack):if stack.head is null:error("Stack is empty")return stack.head.datafunction isEmpty(stack):return stack.head is nullfunction size(stack):return stack.count
#### Tracing Through Operations
#### Time Complexity
All operations are O(1) — instant. You're just updating a pointer or two.
#### No Capacity Limit
The linked list grows as long as your computer has memory. No need to worry about overflow or resizing.
Comparison: Array vs. Linked List
Array-Based Stack
- push / pop / peek: O(1)
- Contiguous memory layout
- Fixed capacity (or resize)
- No extra memory per element
- Excellent cache performance
- Overflow possible with fixed array
contiguous memory
Linked-List Stack
- push / pop / peek: O(1)
- Scattered memory layout
- Unlimited dynamic capacity
- One pointer per node overhead
- Poor cache (pointer chasing)
- Practically no overflow
scattered nodes
Pick array-based when: you know roughly how many items you'll have, or you want the simplest possible implementation.
Pick linked-list-based when: you have no idea how many items you'll need and don't want to deal with resizing.
In practice, most real-world stack implementations (including the ones in programming language standard libraries) use dynamic arrays. They're fast and handle resizing automatically.
A Clean Stack Wrapper
Here's a nice pattern — wrap whichever implementation you choose so the user only sees the clean stack interface:
structure Stack:storage = new ArrayStack() // swap to LinkedStack() if neededfunction push(value):storage.push(value)function pop():return storage.pop()function peek():return storage.peek()function isEmpty():return storage.isEmpty()function size():return storage.size()
The user just calls push and pop. They never need to know what's underneath.
Examples
Example 1: Array-Based — Push Until Full
stack = new ArrayStack(capacity=3)push(stack, 10) // top=0, array=[10, _, _]push(stack, 20) // top=1, array=[10, 20, _]push(stack, 30) // top=2, array=[10, 20, 30]push(stack, 40) // ERROR: Stack is full!
Example 2: Linked List — No Limit
stack = new LinkedStack()push(stack, 10) // [10|null]push(stack, 20) // [20|*]->[10|null]push(stack, 30) // [30|*]->[20|*]->[10|null]push(stack, 40) // [40|*]->[30|*]->[20|*]->[10|null]// Keep going as long as you like
Example 3: Reversing an Array
function reverseArray(arr):stack = new Stack()for each element in arr:stack.push(element)for i from 0 to length(arr) - 1:arr[i] = stack.pop()
Input: [1, 2, 3, 4]. Push all four, then pop — out comes [4, 3, 2, 1].
Common Mistakes
1. Off-by-one with the top index. When top starts at -1, the first push goes to index 0 (top + 1 = 0). When top starts at 0, you need different logic. Pick one convention and stick with it.
2. Not checking for full (array) or empty (both). Every push should check if the array is full. Every pop and peek should check if the stack is empty.
3. Forgetting to update top or count. After push, increment. After pop, decrement. If you forget, size() and isEmpty() give wrong answers.
4. Returning the wrong value on pop. Save the value BEFORE moving the index. value = array[top]; top--; return value is correct. top--; return array[top] returns the wrong element.
Best Practices
Start with the array-based version. It's simpler to write and understand. Only switch to a linked list if you genuinely need unlimited growth.
Use -1 to mean empty. The convention top == -1 means the stack is empty is the most common and least confusing.
Always test the edges. Push onto a full stack. Pop from an empty stack. Push then immediately pop. Single-element operations. These are the cases most likely to break.
Track size properly. Whether it's top + 1 for array or a count variable for linked list, always know how many items are in the stack.
Runnable Code
Here is a complete JavaScript stack implementation you can run. It includes push, pop, peek, isEmpty, and size operations.
class Stack {constructor() {this.items = [];}push(item) {this.items.push(item);}pop() {if (this.isEmpty()) return 'Stack is empty';return this.items.pop();}peek() {if (this.isEmpty()) return 'Stack is empty';return this.items[this.items.length - 1];}isEmpty() {return this.items.length === 0;}size() {return this.items.length;}print() {console.log('Stack (top -> bottom):', [...this.items].reverse().join(', '));}}// --- Demo ---const stack = new Stack();stack.push(10);stack.push(20);stack.push(30);console.log('After push 10, 20, 30:');stack.print();console.log('Peek:', stack.peek());console.log('Pop:', stack.pop());console.log('Pop:', stack.pop());console.log('After two pops:');stack.print();console.log('Size:', stack.size());console.log('isEmpty:', stack.isEmpty());// Reverse an array using a stackconsole.log('\n--- Reverse Array ---');const arr = [1, 2, 3, 4, 5];console.log('Original:', arr.join(', '));const s = new Stack();for (const val of arr) s.push(val);const reversed = [];while (!s.isEmpty()) reversed.push(s.pop());console.log('Reversed:', reversed.join(', '));