
Stack vs Queue: How Developers Stand in Line
Pringles can (Stack) vs Restaurant line (Queue). The most basic data structures, but without them, you can't understand recursion or message queues.

Pringles can (Stack) vs Restaurant line (Queue). The most basic data structures, but without them, you can't understand recursion or message queues.
Why does my server crash? OS's desperate struggle to manage limited memory. War against Fragmentation.

A deep dive into Robert C. Martin's Clean Architecture. Learn how to decouple your business logic from frameworks, databases, and UI using Entities, Use Cases, and the Dependency Rule. Includes Screaming Architecture and Testing strategies.

Two ways to escape a maze. Spread out wide (BFS) or dig deep (DFS)? Who finds the shortest path?

Fast by name. Partitioning around a Pivot. Why is it the standard library choice despite O(N²) worst case?

When I first started learning to code about a month in, I encountered the concepts of Stack and Queue for the first time. But honestly, I didn't get it at first.
"Why can't I just use an array? Why do I need to learn Stack and Queue separately?"
Arrays have push, pop, and shift methods — so why bother learning these abstract concepts called Stack and Queue? It didn't click for me. But over time, I realized something fundamental: Stack and Queue aren't just "containers for data." They represent philosophies about the order in which data is accessed.
And without that philosophy, you can't understand recursion, event loops, message queues, browser back buttons, BFS/DFS algorithms, or how web browsers work. This article documents my journey from confusion to clarity, from struggle to that "aha!" moment when I finally understood what Stack and Queue were all about.
When we store data, there are two major scenarios:
The tools created to solve these two scenarios are Stack and Queue. Sound too basic? Without these two, operating systems, web browsers, and even your messaging apps wouldn't function.
Stack means 'to pile up'. The most perfect analogy is a Pringles potato chip can.
In technical terms, this is called LIFO (Last In, First Out). It's a slightly unfair world where "the guy who came last leaves first".
1. Undo (Ctrl + Z) I want to erase the letter I just typed. The letter typed 'last' needs to be erased 'first', right? So the history of all editors is stored as a stack.
2. Browser Back Button You press the back button while surfing the web. The page visited 'most recently (last)' appears 'first'.
3. Function Call Stack
This is where developers encounter the Stack Overflow error.
If function A calls B, and B calls C, the computer notes down "Finish C then go to B, finish B then go to A." This order is a stack. If you write a recursive function incorrectly, this stack bucket fills up and explodes — that's Stack Overflow.
// Stack in JavaScript
const stack = [];
stack.push("A"); // Insert
stack.push("B");
console.log(stack.pop()); // Eject -> "B" (Last one in comes out first)
When I first learned about recursive functions, I was thrilled. "Functions can call themselves? That's brilliant!" So I wrote a factorial function.
function factorial(n) {
return n * factorial(n - 1);
}
console.log(factorial(5)); // Huh? Maximum call stack size exceeded???
Wait, why is it throwing an error? That's when I realized I had forgotten the base case. The recursive function kept calling itself endlessly, eventually filling up the Call Stack. It was exactly like stuffing Pringles into the can infinitely until the can explodes.
// Correct recursive function
function factorial(n) {
if (n === 0 || n === 1) return 1; // Base Case: termination condition
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
That's when I understood: the stack isn't just a "box to put data in and take data out." It's a mechanism for managing the order of function calls.
JavaScript arrays can easily simulate a stack, but to truly understand how stacks work, it's best to implement one as a class.
class Stack {
constructor() {
this.items = [];
}
push(element) {
this.items.push(element);
}
pop() {
if (this.isEmpty()) {
return "Stack is empty";
}
return this.items.pop();
}
peek() {
// View the top element without removing it
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;
}
clear() {
this.items = [];
}
print() {
console.log(this.items.toString());
}
}
// Usage example
const stack = new Stack();
stack.push("First");
stack.push("Second");
stack.push("Third");
console.log(stack.peek()); // "Third"
console.log(stack.pop()); // "Third"
console.log(stack.size()); // 2
One of the most classic coding interview problems involving stacks is checking if parentheses are balanced. This is LeetCode's Valid Parentheses problem.
function isValid(s) {
const stack = [];
const pairs = {
')': '(',
'}': '{',
']': '['
};
for (let char of s) {
if (char === '(' || char === '{' || char === '[') {
stack.push(char); // Push opening brackets onto the stack
} else {
// When a closing bracket appears
if (stack.length === 0 || stack.pop() !== pairs[char]) {
return false; // Mismatch
}
}
}
return stack.length === 0; // Stack should be empty for valid parentheses
}
console.log(isValid("()")); // true
console.log(isValid("()[]{}")); // true
console.log(isValid("(]")); // false
console.log(isValid("([)]")); // false
console.log(isValid("{[]}")); // true
Opening brackets go onto the stack. When a closing bracket appears, pop from the stack and check if they match. Just like putting Pringles in and taking them out. After solving this problem, I deeply understood how stacks are used in practice.
Queue means 'standing in line'. It's an extremely fair world.
This is called FIFO (First In, First Out). Think of cars passing through a tollgate or a line in front of a famous restaurant.
1. Printer Spool Manager Kim pressed print for a 100-page report. 1 second later, Director Lee pressed print for 1 page. Naturally, Director Lee's job has to wait until all 100 pages of Manager Kim are out. Because order must be guaranteed.
2. Savior of Backend: Message Queue (Kafka, RabbitMQ) Think of a shopping mall server where orders are exploding. If 10,000 people press pay in 1 second, the DB will crash. At this time, the server doesn't process the payment request immediately but stacks them in a Queue first (lines them up). Then, the worker server in the back takes them out one by one according to its capacity and processes them carefully.
Thanks to this structure, the server can endure without crashing. This is called the core of Asynchronous Processing.
// Queue in JavaScript (Using Array)
const queue = [];
queue.push("User1"); // Stand in line
queue.push("User2");
console.log(queue.shift()); // Enter -> "User1" (First one in comes out first)
// Caution: shift() is slow (O(n)). In reality, use a Queue made of Linked List.
When I first learned about queues, I used JavaScript's shift() method. It removes from the front, so it's simple, right? But then I got a "Time Limit Exceeded" error while solving algorithm problems.
Why the timeout? That's when I looked up how shift() works internally.
shift() removes the first element.In contrast, push() only adds to the end, so it's O(1) (very fast). Eventually, I realized that implementing a queue with arrays results in terrible performance due to shift().
To solve the performance issue, you need to build a queue with a Linked List. By managing pointers to the front and rear, both enqueue and dequeue can be done in O(1) time.
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
class Queue {
constructor() {
this.front = null; // Front (where to dequeue)
this.rear = null; // Rear (where to enqueue)
this.size = 0;
}
enqueue(value) {
const newNode = new Node(value);
if (this.isEmpty()) {
this.front = newNode;
this.rear = newNode;
} else {
this.rear.next = newNode; // Link the new node after the current rear
this.rear = newNode; // Update rear to the new node
}
this.size++;
}
dequeue() {
if (this.isEmpty()) {
return "Queue is empty";
}
const removed = this.front.value;
this.front = this.front.next; // Move front to the next node
this.size--;
if (this.size === 0) {
this.rear = null; // If queue is empty, set rear to null
}
return removed;
}
peek() {
if (this.isEmpty()) {
return "Queue is empty";
}
return this.front.value;
}
isEmpty() {
return this.size === 0;
}
getSize() {
return this.size;
}
print() {
if (this.isEmpty()) {
console.log("Queue is empty");
return;
}
let current = this.front;
const values = [];
while (current) {
values.push(current.value);
current = current.next;
}
console.log(values.join(" <- "));
}
}
// Usage example
const queue = new Queue();
queue.enqueue("First");
queue.enqueue("Second");
queue.enqueue("Third");
queue.print(); // First <- Second <- Third
console.log(queue.dequeue()); // "First"
console.log(queue.peek()); // "Second"
console.log(queue.getSize()); // 2
Now both enqueue and dequeue are O(1). Even with 1 million elements, there's no performance degradation. After understanding this implementation, I fully internalized that "queues should be implemented with linked lists, not arrays."
Here's the real core concept. JavaScript's Event Loop is the perfect example of using both stack and queue simultaneously.
The JavaScript engine stacks function calls on the Call Stack in a LIFO manner.
function first() {
console.log("First");
second();
}
function second() {
console.log("Second");
third();
}
function third() {
console.log("Third");
}
first();
Execution order:
first() executes → Push first onto Call Stackconsole.log("First") executes, then second() is called → Push second onto Call Stackconsole.log("Second") executes, then third() is called → Push third onto Call Stackconsole.log("Third") executes, third finishes → Pop third from Call Stacksecond finishes → Pop second from Call Stackfirst finishes → Pop first from Call StackIt's a stack. Like a Pringles can, things pile up and get popped off.
Asynchronous functions (setTimeout, fetch, event listeners, etc.) go into the Callback Queue when they complete. FIFO style.
console.log("Start");
setTimeout(() => {
console.log("Timeout 1");
}, 0);
setTimeout(() => {
console.log("Timeout 2");
}, 0);
console.log("End");
Output:
Start
End
Timeout 1
Timeout 2
Why this order?
console.log("Start") executes (Call Stack)setTimeout is asynchronous → Handed off to Web APIconsole.log("End") executes (Call Stack)Timeout 1 executes, then Timeout 2 executesIt's a queue. First callback in, first callback out.
Promise goes into the Microtask Queue, not the Callback Queue. It has higher priority.
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Output:
Start
End
Promise
Timeout
Why? Because the Microtask Queue is processed before the Callback Queue.
After understanding this structure, I fully grasped that "JavaScript runs on one stack and two queues." The event loop is the perfect real-world example of stacks and queues working together.
When you study data structures, you inevitably encounter two algorithms: BFS (Breadth-First Search) and DFS (Depth-First Search).
function dfs(graph, start) {
const stack = [start];
const visited = new Set();
while (stack.length > 0) {
const node = stack.pop(); // Pop from Stack (LIFO)
if (visited.has(node)) continue;
console.log(node); // Visit
visited.add(node);
// Add neighbor nodes to stack (last in, first explored)
for (const neighbor of graph[node].reverse()) {
if (!visited.has(neighbor)) {
stack.push(neighbor);
}
}
}
}
const graph = {
A: ['B', 'C'],
B: ['D', 'E'],
C: ['F'],
D: [],
E: [],
F: []
};
dfs(graph, 'A'); // A B D E C F (depth-first)
function bfs(graph, start) {
const queue = [start];
const visited = new Set();
while (queue.length > 0) {
const node = queue.shift(); // Dequeue from Queue (FIFO)
if (visited.has(node)) continue;
console.log(node); // Visit
visited.add(node);
// Add neighbor nodes to queue (first in, first explored)
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
queue.push(neighbor);
}
}
}
}
bfs(graph, 'A'); // A B C D E F (breadth-first)
DFS digs deep like a Pringles can, and BFS spreads wide like a restaurant line. After understanding this difference, I deeply internalized that "stacks and queues are the foundation of algorithms."
A queue where you can insert and remove from both ends. It's a hybrid of stack + queue.
class Deque {
constructor() {
this.items = [];
}
addFront(element) {
this.items.unshift(element);
}
addRear(element) {
this.items.push(element);
}
removeFront() {
return this.items.shift();
}
removeRear() {
return this.items.pop();
}
peekFront() {
return this.items[0];
}
peekRear() {
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
}
Unlike a normal queue, elements with higher priority come out first. Think of a hospital emergency room: critical patients are treated before people who arrived earlier.
class PriorityQueue {
constructor() {
this.items = [];
}
enqueue(element, priority) {
const queueElement = { element, priority };
let added = false;
for (let i = 0; i < this.items.length; i++) {
if (queueElement.priority < this.items[i].priority) {
this.items.splice(i, 0, queueElement);
added = true;
break;
}
}
if (!added) {
this.items.push(queueElement);
}
}
dequeue() {
return this.items.shift();
}
front() {
return this.items[0];
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
}
// Usage example
const pq = new PriorityQueue();
pq.enqueue("Task A", 2);
pq.enqueue("Task B", 1); // Higher priority
pq.enqueue("Task C", 3);
console.log(pq.dequeue()); // Task B (priority 1)
console.log(pq.dequeue()); // Task A (priority 2)
In a fixed-size queue, space is reused. When you reach the end of the array, it wraps around to the beginning.
class CircularQueue {
constructor(size) {
this.items = new Array(size);
this.size = size;
this.front = -1;
this.rear = -1;
}
enqueue(element) {
if ((this.rear + 1) % this.size === this.front) {
console.log("Queue is full");
return;
}
if (this.front === -1) this.front = 0;
this.rear = (this.rear + 1) % this.size;
this.items[this.rear] = element;
}
dequeue() {
if (this.front === -1) {
console.log("Queue is empty");
return;
}
const removed = this.items[this.front];
if (this.front === this.rear) {
this.front = -1;
this.rear = -1;
} else {
this.front = (this.front + 1) % this.size;
}
return removed;
}
isFull() {
return (this.rear + 1) % this.size === this.front;
}
isEmpty() {
return this.front === -1;
}
}
Q1. What's the difference between Stack and Queue? A. Stack is LIFO (Last In, First Out), Queue is FIFO (First In, First Out). Stack: last thing in comes out first. Queue: first thing in comes out first.
Q2. Why does implementing a queue with JavaScript arrays cause performance issues?
A. The shift() method removes the first element and then shifts all remaining elements forward by one position, resulting in O(n) time complexity. Implementing with a linked list reduces it to O(1).
Q3. What's the relationship between recursion and stacks? A. Recursive functions use the Call Stack internally. Each time a function calls itself, it's pushed onto the stack, and when the base case is reached, they pop off in reverse order. Without a base case, you get Stack Overflow.
Q4. What's the difference between BFS and DFS? A. DFS uses a stack (or recursion) for depth-first traversal, while BFS uses a queue for breadth-first traversal. BFS is better for finding shortest paths, DFS is better for exploring all paths.
Q5. When do you use a Priority Queue? A. For task scheduling with priorities, shortest path algorithms like Dijkstra's, compression algorithms like Huffman coding, etc.
Q6. What's the difference between Microtask Queue and Callback Queue in JavaScript's Event Loop? A. Microtask Queue holds Promises and queueMicrotask, while Callback Queue holds setTimeout, setInterval, DOM events, etc. Event Loop checks Microtask Queue first when Call Stack is empty, then Callback Queue.
1. Message Queue Systems (Kafka, RabbitMQ) When handling massive traffic, message queues are essential. Orders, payments, notifications go into queues and workers process them sequentially.
2. Browser History Management Back/Forward buttons are implemented with two stacks: current page stack and forward stack.
3. Redux Middleware Middleware like Redux Thunk and Saga queue actions and process them in order.
4. CPU Scheduling Operating systems queue processes and handle them according to priority. Round Robin, Priority Scheduling, etc., are all queue-based.
Sometimes I think about life. The experience and knowledge we've accumulated are like a Stack. We use the knowledge learned most recently first. (Calculus learned in childhood is stuck at the bottom of the stack and never comes out.)
On the other hand, the opportunities we wait for come like a Queue. If you don't get impatient and stand in line preparing, your turn (Pop) will come someday.
Is your code stacking data (Stack) or lining it up (Queue) right now? Bringing out the right tool for the situation is a developer's ability.
I used to think "Why can't I just use an array?" but now I've fully internalized that Stack and Queue are philosophies of data structures. And I've come to understand that this philosophy permeates everything: recursion, event loops, message queues, BFS/DFS, and more. This was it. Stack and Queue aren't just containers — they're the two fundamental ways computers handle data.