You are learning about data structures and have already studied the basics, including lists, arrays, and linked lists. If so, you are ready to read this article.
The next step is to explore more specialized types of collections.
In this article, we cover Stacks and Queues.
Some languages, such as Java, C++, and C#, have built-in support for stacks and queues.
Other languages, like JavaScript, use the array data type with methods that can emulate stacks and queues. In Python, the collections package provides the deque class, which can function as a stack or a queue. For languages without built-in support, stacks and queues can be implemented by creating a class with a list as an attribute and adding specific methods.

Stack

A stack is a collection of items that follows the principle of Last In, First Out (LIFO).
A stack functions like a pile of books where books can only be added to the top and removed from the top. The last book added to the pile is the first one that can be removed.

A stack typically has two primary methods:
- push(): Adds an element to the top of the stack.
- pop(): Removes the element from the top of the stack and returns its value.

Additionally, there is an optional method:
- peek(): Returns the element at the top of the stack without removing it.

Some languages may also implement the following (though these are not standardized):
- length() or size(): Returns the number of elements in the stack.
- empty() or isEmpty(): Returns true if the stack is empty.

If a pop() is called on an empty stack, the behavior depends on the language. Some languages return a null or default value, while others raise an exception.

You can alternate push() and pop() operations freely as long as you avoid calling pop() on an empty stack.
In pseudo-code it would look like this:

stack = []
# empty stack

stack.push('Moby Dick')
# stack has one element

stack.push('War and Peace')
# stack has 2 elements

print(stack.pop())
# prints 'War and Peace' and it only has 'Moby Dick'

print(stack.peek())
# prints 'Moby Dick' and it doesn't changes the stack

stack.push('Meditations')
# stack as 2 elements

How to implement a Stack in Java

Java provides the Stack class in the java.util package, which extends Vector and implements the List interface.

import java.util.Stack;

public class Main {
    public static void main(String[] args) {
        Stack<String> bookStack = new Stack<>();

        bookStack.push("Moby Dick");
        bookStack.push("War and Peace");
        bookStack.push("Meditations");

        System.out.println("Top book: " + bookStack.peek()); // Displays Meditations
        System.out.println("Popped book: " + bookStack.pop()); // Displays Meditations and removes it
        System.out.println("New top book: " + bookStack.peek());
    }
}

How to implement a Stack in JavaScript

JavaScript does not have a built-in stack class, but you can use an array with methods like push() and pop() which mimic stack operations:

let bookStack = [];

bookStack.push('Moby Dick');
bookStack.push('War and Peace');
bookStack.push('Meditations');

// JavaScript has no peek method
console.log("Top book:", bookStack[bookStack.length - 1]); // peek operation
console.log("Popped book:", bookStack.pop());
console.log("New top book:", bookStack[bookStack.length - 1]); // peek after pop

How to implement a Stack in Python

Python doesn’t have a built-in implementation of a stack but it has collections.deque which can be used both for stacks and queues.
An alternative it’s to implement it as a class having a list as an attribute, this implementation is slower but enforces the usage of only the stack methods.

from collections import deque

book_stack = deque()

# python uses append method to add elements to the stack instead of push
book_stack.append('Moby Dick')
book_stack.append('War and Peace')
book_stack.append('Meditations')

# python doesn't have a peek method
print("Books in the stack (top to bottom):")
while len(book_stack):
    print(book_stack.pop())

# It will display:
# Meditations
# War and Peace
# Moby Dick

# ------------------------------------------------------
# Implementation as a class
# ------------------------------------------------------

class Stack:
    def __init__(self):
        self._items = []

    def push(self, item):
        self._items.append(item)

    def pop(self):
        if not self.is_empty():
            return self._items.pop()
        else:
            raise IndexError("Stack is empty")

    def peek(self):
        return self._items[-1] if not self.is_empty() else None

    def is_empty(self):
        return len(self._items) == 0

book_stack = Stack()

book_stack.push('Moby Dick')
book_stack.push('War and Peace')
book_stack.push('Meditations')

print("Top book:", book_stack.peek()) # Displays Meditations but doesn't removes
print("Popped book:", book_stack.pop()) # Displays Meditations and removes it
print("Popped book:", book_stack.pop()) # Displays War and Peace and removes it

Use Cases of Stacks

Stacks operate on the LIFO (Last In, First Out) principle, where the last element added is the first one removed. They are suitable for tasks that require reversing or tracking execution flow.

  1. Expression Evaluation and Parsing:
  1. Convert infix expressions to postfix/prefix.
  2. Evaluate postfix expressions.
  1. Undo Operations in Text Editors:
  1. Store states of text for undo operations.
  1. Browser Navigation:
  1. Maintain history of visited pages (back/forward navigation).
  1. Call Stack in Recursion:
  1. Manage function calls and local variables during recursive execution.
  1. Parenthesis Matching:
  1. Validate balanced brackets in code or mathematical expressions.
  1. Reversing Data:
  1. Reverse strings, linked lists, or other data.
  1. Syntax Parsing in Compilers:
  1. Manage tokens for syntax checking and parsing.

Queue

A queue is a collection of items that follows the principle of First In, First Out (FIFO).
A queue is better visualized as a row rather than a pile. Unlike a stack, where items are added to the top, in a queue, items are added to the back and removed from the front, similar to a line of people.
In fact, a line of people is an excellent analogy for how a queue works as an abstract data type: people join the queue at the back and are attended to at the front. The last person to arrive must wait until everyone ahead of them has been served.
A queue typically has two primary methods:
- enqueue(): Adds an element to the back of the queue.
- dequeue(): Removes the element from the front of the queue and returns its value.
Additionally, there is an optional method:
- peek() or front(): Returns the element at the front of the queue without removing it.
Some languages may also implement the following (though these are not standardized):
- length() or size(): Returns the number of elements in the queue.
- empty() or isEmpty(): Returns true if the queue is empty.
If a dequeue() operation is called on an empty queue, the behavior depends on the language. Some languages return a null or default value, while others raise an exception.
You can freely interleave enqueue() and dequeue() operations as long as you avoid calling dequeue() on an empty queue.

In pseudo-code it would look like this:

queue = []
# empty queue

queue.enqueue('James')
# queue has one element

queue.enqueue('Mary')
# queue has 2 elements: 'Mary', 'James'

queue.enqueue('Victor')
# queue has 3 elements

print(queue.dequeue())
# prints 'James' and it has 'Victor' and 'Mary'

print(queue.peek())
# prints 'Mary' and it doesn't changes the queue

queue.enqueue('James')
# queue has 3 elements: 'James', 'Victor', 'Mary'

How to implement a Queue in Java

Java doesn’t have a Queue class like it does for Stack, but it provides a Queue interface along with several classes that implement queue functionality, such as LinkedList, PriorityQueue, and ArrayDeque.
The choice of class depends on its intended usage and whether thread safety is required.
For example:

  • The LinkedList class offers a simple and efficient way to implement a queue if multithreading isn’t needed.
  • As a learner, LinkedList is often the best option to start with.

The Queue interface in Java also uses specific method names for queue operations:

  • offer(): Adds an element to the back of the queue.
  • poll(): Removes and returns the element at the front of the queue.

import java.util.LinkedList;
import java.util.Queue;

public class Main {
    public static void main(String[] args) {
        Queue<String> peopleQueue = new LinkedList<>();

        peopleQueue.offer("James");
        peopleQueue.offer("Mary");
        peopleQueue.offer("Victor");

        System.out.println("Front person: " + peopleQueue.peek()); // Displays James
        System.out.println("Removed person: " + peopleQueue.poll()); // Removes and displays James
        System.out.println("New front person: " + peopleQueue.peek()); // Displays Mary
    }
}

How to implement a Queue in JavaScript

JavaScript does not have a built-in Queue class, but you can use an array to mimic queue operations using methods like push() and shift() to implement enqueue() and dequeue().
For better encapsulation, you can create a wrapper class to define enqueue() and dequeue() methods, preventing access to other array methods like pop(), which could break the queue's principles.

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

    enqueue(element) {
        this.items.push(element);
    }

    dequeue() {
        if (this.isEmpty()) {
            return "Queue is empty";
        }
        return this.items.shift();
    }

    peek() {
        if (this.isEmpty()) {
            return "Queue is empty";
        }
        return this.items[0];
    }

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

const peopleQueue = new Queue();

peopleQueue.enqueue("James");
peopleQueue.enqueue("Mary");
peopleQueue.enqueue("Victor");

console.log("Front person: " + peopleQueue.peek()); // Displays James
console.log("Removed person: " + peopleQueue.dequeue()); // Removes and displays James
console.log("New front person: " + peopleQueue.peek()); // Displays Mary

How to implement a Queue in Python

Python offers multiple ways to implement a queue:

  1. Using the Queue class from the queue module:
  • This implementation is thread-safe, making it suitable for multithreaded environments.
  • However, it doesn’t support a peek() method and comes with the overhead of multithreading support.
  1. Using the deque class from the collections module:
  • This is a faster implementation, supporting peek() and other queue operations efficiently.
  • However, it does not enforce strict queue behavior, allowing deviations from the standard principles.

from collections import deque
import queue

people_queue = queue.Queue()
people_queue.put("James")
people_queue.put("Mary")
people_queue.put("Victor")

print(people_queue.get())  # Output: James
print(people_queue.get())  # Output: Mary

# ------------------------------------------------------
# Implementation using collections.deque
# ------------------------------------------------------

people_queue = deque()
people_queue.append("James")
people_queue.append("Mary")
people_queue.append("Victor")

print("Front person: " + people_queue[0])  # Displays James
print("Removed person: " + people_queue.popleft())  # Removes and displays James
print("New front person: " + people_queue[0])  # Displays Mary

Use Cases of Queues

Queues operate on the FIFO (First In, First Out) principle, where the first element added is the first one removed. They are used in scenarios involving order preservation and sequential processing.

  1. Scheduling Tasks:
  • CPU scheduling (e.g., round-robin).
  • Print job management in a printer queue.
  1. Data Buffers:
  • Buffers in IO operations or network communication.
  • Streaming services like video or audio playback.
  1. Asynchronous Processing:
  • Message queues in distributed systems (e.g., RabbitMQ, Kafka).
  1. Order Processing Systems:
  • Task queues in online systems for order fulfillment.
  1. Simulation of Real-World Queues:
  • Customer service lines, ticket counters.
  1. Load Balancing:
  • Distributing tasks among multiple servers or processes.
  1. Real-Time Systems:
  • Event-driven systems like real-time monitoring or logging.

Practical Example of Stack Operations

One common application of stacks is implementing an undo functionality. In the project below, JavaScript manages a draggable element that records each drop position in the positionStack variable. Each new position is pushed onto the stack as the element is moved. The undo button uses this stack to revert the element to its previous position by removing the last recorded entry. This project demonstrates how stacks can effectively track and manage changes, making them essential for handling undo operations in various applications.

You can see it in action on this CodePen.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Draggable Div with Undo</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
        .draggable {
            position: absolute;
            top: 100px;
            left: 100px;
            width: 100px;
            height: 50px;
            border: 2px solid black;
            text-align: center;
            line-height: 50px;
            cursor: grab;
            user-select: none;
        }
        button {
            position: absolute;
            top: 10px;
            left: 10px;
            padding: 10px;
        }
    </style>
</head>
<body>

<div id="draggable" class="draggable">Drag Me</div>
<button id="undo">Undo</button>

<script>
    const draggable = document.getElementById('draggable');
    const undoButton = document.getElementById('undo');
    const positionStack = [];

    let isDragging = false;
    let offsetX = 0;
    let offsetY = 0;

    draggable.addEventListener('mousedown', (event) => {
        isDragging = true;
        offsetX = event.clientX - draggable.offsetLeft;
        offsetY = event.clientY - draggable.offsetTop;
        positionStack.push({ top: draggable.style.top, left: draggable.style.left });
    });

    document.addEventListener('mousemove', (event) => {
        if (isDragging) {
            draggable.style.top = `${event.clientY - offsetY}px`;
            draggable.style.left = `${event.clientX - offsetX}px`;
        }
    });

    document.addEventListener('mouseup', () => {
        isDragging = false;
    });

    undoButton.addEventListener('click', () => {
        if (positionStack.length > 0) {
            const lastPosition = positionStack.pop();
            draggable.style.top = lastPosition.top;
            draggable.style.left = lastPosition.left;
        }
    });
</script>

</body>
</html>