Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
231: Collections Module: deque
Look, we've all been there. You need a simple queue—first in, first out—to handle some tasks or track a history of events, and your first instinct is to reach for a standard Python list. It's convenient, it's familiar, and for a few dozen items, it works perfectly. But as soon as you scale that logic to thousands of entries, you're going to hit a performance wall that isn't immediately obvious from the code itself.
The Hidden Cost of popping from the front
Imagine you're building a system to track the last 1,000 logs from a server. You use a list, append() new logs to the end, and when the list gets too long, you call logs.pop(0) to remove the oldest entry. On the surface, this looks clean. However, under the hood, Python lists are contiguous arrays. When you remove the element at index 0, every single remaining element in that list has to be shifted one position to the left to fill the gap.
# The naive way
logs = []
for i in range(100000):
logs.append(f"Log entry {i}")
if len(logs) > 1000:
logs.pop(0) # This is the silent killer
In computer science terms, pop(0) is an O(n) operation. If your list has 1,000 items, Python performs 999 shifts every time you remove the oldest log. If your list grows to 100,000, that's 99,999 shifts per removal. You'll notice your CPU spiking and your application lagging, and you'll be scratching your head because the logic seems so simple.
Why the deque is the right tool
This is exactly why the collections module gives us the deque (short for "double-ended queue"). Unlike a list, a deque is implemented as a doubly-linked list. This means adding or removing elements from either the beginning or the end happens in O(1) time. It doesn't matter if you have ten items or ten million; popping from the left is just as fast as popping from the right.
from collections import deque
# The professional way
logs = deque()
for i in range(100000):
logs.append(f"Log entry {i}")
if len(logs) > 1000:
logs.popleft() # Instantaneous, regardless of size
I want to point out popleft() specifically. That's the deque equivalent of pop(0), but without the baggage of shifting elements. If you're moving things from one end to the other, this is your go-to tool.
Handling fixed-size buffers automatically
Now, here is my favorite feature of the deque that most people overlook: the maxlen argument. In the previous examples, I manually checked the length of the collection and popped the old item. But if you know your buffer should only ever hold, say, 1,000 items, you can bake that constraint right into the object.
# No more manual length checks
logs = deque(maxlen=1000)
for i in range(100000):
logs.append(f"Log entry {i}")
# When the 1001st item arrives, the 1st item is
# automatically discarded from the opposite end.
This isn't just cleaner code; it's more robust. You remove the risk of forgetting the if len(logs) > limit check in some other part of your app. When you need a "sliding window" of data, maxlen is the most elegant way to handle it in Python.
📋 Practical Task
Implementing a Fixed-Size Undo Buffer
You are building a simple text editor engine. To support an "Undo" feature, you need to keep track of the last 20 actions the user performed (e.g., "Typed 'A'", "Deleted Line 5", "Pasted Text"). If the user performs a 21st action, the oldest action should be forgotten automatically to save memory.
Your Task: Create a class called UndoManager. It should:
- Initialize a
dequewith a maximum length of 20. - Have a method
record_action(action_text)that adds a new action to the buffer. - Have a method
undo()that removes and returns the most recent action from the buffer. If the buffer is empty, it should returnNone. - Have a method
get_history()that returns the current buffer as a standard Python list.
Test your implementation by recording 25 different actions and verifying that only the last 20 remain in the history.
There are no comments for now.