JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
170: Implementing a Stack and Queue
When you first start working with data structures in JavaScript, the temptation is to use an Array for everything. I get it—Arrays are flexible, they're built-in, and for 90% of the tasks you'll face, they're more than enough. But once you start implementing specific patterns like Stacks and Queues, you have to stop thinking about "what works" and start thinking about "how it scales."
The Stack: When Arrays Actually Fit
A Stack is the simplest of these two. It's a Last-In, First-Out (LIFO) structure. Think of it like a stack of plates; you add one to the top, and the first one you take off is that same top plate. A great real-world example is the "Undo" feature in a text editor. Every time you type a character or delete a word, you push that action onto a stack. When you hit Ctrl+Z, you pop the most recent action off the top to reverse it.
In JavaScript, the native Array is actually a perfect fit for a Stack. Why? Because push() and pop() both operate on the end of the array. They don't require the engine to move any other elements around. It's an O(1) operation, meaning it takes the same amount of time whether your stack has ten items or ten million.
class UndoStack {
constructor() {
this.actions = [];
}
pushAction(action) {
this.actions.push(action);
}
undo() {
if (this.actions.length === 0) return null;
return this.actions.pop();
}
}
The Queue Trap: The Cost of .shift()
Queues are different. They are First-In, First-Out (FIFO). Imagine a line at a coffee shop: the first person in line is the first person served. If you're building a background task processor—say, a system that handles image uploads one by one—you need a Queue.
The "naive" way to do this is to use push() to add items to the end and shift() to remove them from the front. It looks clean, and for a few dozen items, you'll never notice a problem. But here is where it breaks. In JavaScript, arrays are indexed. When you call shift(), the engine removes the element at index 0 and then has to move every single remaining element one slot to the left to fill the gap. That's an O(n) operation.
I've seen this cause massive performance bottlenecks in production. If your queue grows to 100,000 tasks, every single "dequeue" operation forces the engine to re-index 99,999 elements. Your event loop will choke, and your application will stutter.
Scaling the Queue with Pointers
To fix this, we need to stop moving the data and start moving our "view" of the data. Instead of shifting the array, we can use a JavaScript object to store our items and maintain two pointers: a head (where we take things out) and a tail (where we put things in). By incrementing these pointers, we effectively "remove" an item by simply ignoring its old index, without ever touching the rest of the collection.
This turns our dequeue operation from O(n) back into O(1). We trade a tiny bit of memory (since the object keeps growing until we manually clear it or the object is garbage collected) for a massive gain in speed.
class TaskQueue {
constructor() {
this.queue = {};
this.head = 0;
this.tail = 0;
}
enqueue(task) {
this.queue[this.tail] = task;
this.tail++;
}
dequeue() {
if (this.head === this.tail) return null;
const task = this.queue[this.head];
delete this.queue[this.head]; // Clean up memory
this.head++;
return task;
}
get length() {
return this.tail - this.head;
}
}
Notice how the dequeue method now just accesses a key and deletes it. No re-indexing, no shifting, no performance cliff. When you're choosing between these two approaches, always ask yourself: "How large could this collection possibly get?" If the answer is "thousands," stay away from shift().
📋 Practical Task
Implementing a High-Performance Print Spooler
You are building a print spooler for an office. The system receives print jobs from multiple computers and must process them in the exact order they arrived. Because the office is huge, the spooler might handle thousands of jobs per hour.
Your Task: Create a class named PrintSpooler that implements a Queue using the pointer-based approach described in the lesson. Your class must include the following:
- An
addJob(documentName)method that adds a document to the end of the queue. - A
processNextJob()method that removes and returns the document at the front of the queue. If the queue is empty, it should return the string"No jobs pending". - A
getPendingCount()method that returns the number of jobs currently waiting.
Test your implementation by adding five different document names and processing three of them, ensuring that the jobs are returned in the correct order and the pending count updates accurately.
There are no comments for now.