Skip to Content
Course content

170: Implementing a Stack and Queue

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.