Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 3

Deque for Stacks and Queues

Recommended resources

Hello! Welcome to the third lesson in our "Python Tools for Competitive Programming" module.

In our previous lessons, we've focused on writing concise and powerful code using list comprehensions and lambda functions. Today, we shift our focus from code style to a fundamental aspect of competitive programming: performance. You'll discover that Python's standard list isn't always the best tool for the job, especially when you need to add or remove items from the beginning of a sequence.

This lesson introduces collections.deque, a high-performance data structure from Python's standard library. You will learn why it's the ideal choice for implementing efficient queues (First-In, First-Out) and stacks (Last-In, First-Out), which are essential building blocks for countless algorithms you'll encounter.

1. The Problem: Why Lists Can Be Inefficient

Let's start by considering a queue. A queue is a collection that follows the "First-In, First-Out" (FIFO) principle. Think of a line of people waiting for a bus: the first person to join the line is the first person to get on the bus.

You can simulate a queue with a Python list:

  • Enqueue (add to the end): my_list.append(item)
  • Dequeue (remove from the front): my_list.pop(0)

While append() is generally fast, pop(0) is very slow. When you remove the first element from a list, every other element in the list has to be shifted one position to the left. For a list with n elements, this is an O(n) operation. In competitive programming, where your code might process millions of items, this inefficiency can lead to a "Time Limit Exceeded" error.

The following video clip demonstrates this issue and sets the stage for a better solution.

Queue - Data Structures & Algorithms Tutorials In Python #8

This video from codebasics, titled "Queue - Data Structures & Algorithms Tutorials In Python #8," starts by explaining what a queue is and then shows why using a standard Python list for it is problematic.

Watch the segment from 05:30 to 07:42. Pay close attention to the demonstration of using list.insert(0, ...) and list.pop() and the explanation of the problems associated with this approach.


2. The Solution: collections.deque

To solve this performance problem, Python's collections module provides the deque (pronounced "deck") object, which stands for "double-ended queue".

A deque is specifically designed for fast additions and removals from both ends. It is implemented internally as a doubly linked list, which means that adding or removing an element from either the beginning or the end takes the same amount of time, regardless of the deque's size. This is a constant time, or O(1), operation.

# First, you need to import it
from collections import deque

The key methods for a deque are:

  • append(item): Adds an item to the right end. (O(1))
  • pop(): Removes an item from the right end. (O(1))
  • appendleft(item): Adds an item to the left end. (O(1))
  • popleft(): Removes an item from the left end. (O(1))

This image provides a clear visual of how deque operations work.

Deque Operations: Queue and Stack Functionality
This diagram illustrates that a `deque` allows for efficient operations like append and pop on both its left and right sides.

The following short video brilliantly explains why a deque is so much faster for these operations compared to a list.

The Best Data Structure You’ve Never Heard of | Python Deques

The video "The Best Data Structure You’ve Never Heard of | Python Deques" by Daniel Boctor gives an excellent conceptual overview of the performance differences.

Watch from 01:37 to 02:41. This segment visualizes how a list needs to shift all its elements (O(n)) while a deque can simply adjust its internal pointers (O(1)) for left-side operations.

The performance difference is not minor. For operations at the start of a large collection, a deque can be thousands of times faster than a list.

Performance Comparison: Python List vs. collections.deque for Left-Side Insertion
A `timeit` comparison showing that `deque.appendleft()` is orders of magnitude faster (nanoseconds vs. microseconds) than `list.insert(0, ...)`, which is essential knowledge for competitive programming.

3. Implementing a Queue with deque

With deque, implementing an efficient FIFO queue is straightforward and should be your default approach in Python.

  • Enqueue: Add to the right with append().
  • Dequeue: Remove from the left with popleft().
from collections import deque

# Initialize an empty queue
customers = deque()

# Customers arrive and are enqueued
customers.append("Alice")
customers.append("Bob")
customers.append("Charlie")

print("Current queue:", customers)  # Output: deque(['Alice', 'Bob', 'Charlie'])

# Customers are served and dequeued
first_customer = customers.popleft()
print(f"Serving: {first_customer}") # Output: Serving: Alice

second_customer = customers.popleft()
print(f"Serving: {second_customer}") # Output: Serving: Bob

print("Remaining queue:", customers) # Output: deque(['Bob', 'Charlie'])

Both append and popleft are O(1) operations, making this implementation highly performant.

For a deeper dive, the following article provides a thorough guide and even shows how to wrap a deque in a custom class for a cleaner interface.

Python's deque: Implement Efficient Queues and Stacks

The article "Python's deque: Implement Efficient Queues and Stacks" from Real Python is an excellent resource. We'll focus on the section dedicated to building queues.

Read the section titled "Build Efficient Queues With deque". Pay attention to the restaurant queue example and the custom Queue class implementation, which is a great pattern to know.


4. Implementing a Stack with deque

A stack is a "Last-In, First-Out" (LIFO) data structure. Think of a stack of plates: you add a new plate to the top and you also remove a plate from the top.

A standard Python list can be used as an efficient stack:

  • Push (add to top): my_list.append(item)
  • Pop (remove from top): my_list.pop()

Both of these are (amortized) O(1) operations. So, for stacks, a list is perfectly fine.

However, you can also use a deque to implement a stack. This can be useful for consistency if you are using deques for queues elsewhere in your code.

  • Push: my_deque.append(item)
  • Pop: my_deque.pop()
from collections import deque

# Initialize an empty stack
history = deque()

# User visits pages (push)
history.append("google.com")
history.append("wikipedia.org")
history.append("realpython.com")

print("Browser history:", history) # Output: deque(['google.com', 'wikipedia.org', 'realpython.com'])

# User clicks the back button (pop)
last_page = history.pop()
print(f"Went back from: {last_page}") # Output: Went back from: realpython.com

print("Current history:", history) # Output: deque(['google.com', 'wikipedia.org'])

As with the list-based stack, these operations are O(1).


5. deque vs. list: A Quick Reference

For competitive programming, choosing the right data structure is critical. Here's a summary of the performance characteristics to help you decide.

Deque in Python: A Comprehensive Guide to Efficient Data ...

The article "Deque in Python" from AlgoCademy provides a concise table comparing the time complexities of deque and list.

Find the section titled "6. Deque vs. List: Performance Comparison" and study the table. This is the key takeaway of the lesson.

The rule of thumb: If you need to add or remove elements from the left side (the beginning) of a sequence, use a deque. For all other list-like purposes, a list is usually sufficient.

Test your understanding!

A palindrome is a word or phrase that reads the same forwards and backwards, like "racecar" or "level".

Using a deque, write a function is_palindrome(s: str) -> bool that checks if a string is a palindrome. Your function should work by adding all characters of the string to a deque and then repeatedly comparing and removing the characters from both ends.

Show answer
from collections import deque

def is_palindrome(s: str) -> bool:
    # Create a deque from the string characters
    char_deque = deque(s)

    # While there are at least two characters left
    while len(char_deque) > 1:
        # Pop from both ends and compare
        if char_deque.popleft() != char_deque.pop():
            return False
            
    # If the loop finishes, it's a palindrome
    return True

# Test cases
print(is_palindrome("racecar"))  # Output: True
print(is_palindrome("hello"))    # Output: False
print(is_palindrome("level"))    # Output: True

This solution is elegant because it directly uses the double-ended nature of the deque to solve the problem.


Conclusion

You've now learned about one of the most important performance tools in Python's standard library. Mastering deque is a significant step in writing code that is not only correct but also fast enough to pass the stringent time limits of competitive programming platforms.

Key Takeaways:

  • Queues (FIFO): Always use a deque for efficient queues. Use append() to enqueue and popleft() to dequeue.
  • Stacks (LIFO): A standard list works well (append and pop), but a deque is also a perfect fit (append and pop).
  • Performance: deque provides O(1) time complexity for adding and removing elements from both ends, while a list is O(n) for operations at its beginning.
  • When to use deque: Whenever you anticipate frequent additions or removals from the front of a sequence.

Up Next: We will continue our exploration of the collections module with collections.Counter, a specialized dictionary subclass that is incredibly useful for counting hashable objects—a very common task in algorithmic problems.