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
44: Choosing the Right Collection Type
I've spent a lot of time reviewing code from developers who are technically proficient but still fall into the "list trap." It happens because lists are the most intuitive collection in Python—they're just a sequence of things. When you aren't sure what to use, your brain defaults to a list. But as your data grows, using a list for the wrong job is one of the fastest ways to kill your application's performance.
Let's look at a real-world scenario: you're building a moderation system for a forum, and you have a collection of 10,000 banned user IDs. Every time a user tries to post a comment, you need to check if their ID is in that banned list.
The Cost of Scanning a List
# The naive approach
banned_users = [102, 405, 992, 1004, ...] # Imagine 10,000 IDs here
def can_post(user_id):
if user_id in banned_users:
return False
return True
On the surface, this looks perfectly clean. But here is what's happening under the hood: the in operator for a list performs a linear search. Python starts at index 0 and asks, "Is this the ID?" then moves to index 1, then 2, and so on. If the user isn't banned, Python has to check every single one of those 10,000 elements before it can confidently tell you the user is allowed to post. I call this "walking the line." It's fine for ten items, but when you're doing this for every single request on a high-traffic site, you're wasting a massive amount of CPU cycles on a simple membership check.
Why the Set Changes Everything
If you don't care about the order of the IDs and you know they should be unique, a set is almost always the correct choice for membership tests. A set doesn't "walk the line"; it uses a hash table.
# The professional approach
banned_users = {102, 405, 992, 1004, ...} # A set literal
def can_post(user_id):
if user_id in banned_users:
return False
return True
The syntax is nearly identical—just curly braces instead of square ones—but the performance difference is staggering. Looking up an item in a set takes constant time, $O(1)$, regardless of whether you have ten users or ten million. The computer calculates a hash of the ID and jumps directly to the memory location where that ID would be. You've effectively traded a tiny bit of memory for a massive increase in speed.
Mapping Values instead of Just Checking Existence
Now, what happens if you don't just need to know if a user is banned, but why they were banned? This is where you move from a set to a dictionary. I see people try to solve this by keeping two lists in parallel (one for IDs and one for reasons), which is a nightmare to maintain. If you sort one and forget the other, your data is corrupted.
A dictionary lets you associate the unique key (the user ID) with a value (the ban reason). Like sets, dictionaries use hash tables, so the lookup speed remains $O(1)$. You get the performance of a set with the added benefit of structured data.
# Mapping IDs to reasons
banned_users = {
102: "Spamming",
405: "Harassment",
992: "Terms of Service violation"
}
def get_ban_status(user_id):
# .get() prevents the program from crashing if the ID isn't found
reason = banned_users.get(user_id)
if reason:
return f"Banned for: {reason}"
return "Active"
Choosing Based on the Constraint
So, how do you decide in the moment? I usually ask myself three questions. First: Do I need to maintain the order of elements? If yes, stick with a list. Second: Do I need to ensure every element is unique? If yes, use a set. Third: Do I need to associate a piece of data with a unique identifier? Use a dictionary.
One last thing: don't forget about tuples. I use them when I want to signal to other developers (and my future self) that this collection should not change. If you have a set of coordinates for a map or a fixed configuration of server ports, a tuple is a safer bet than a list because it's immutable. It prevents accidental .append() calls from breaking your logic elsewhere in the app.
📋 Practical Task
Refactoring the Inventory Audit System
You've been handed a script that audits a warehouse inventory. The current implementation uses a list of tuples to store product IDs and their current stock levels. The script is running incredibly slowly because it iterates through the entire list every time it needs to update a stock count.
Your Task: Refactor the update_stock function. Instead of using a list of tuples, convert the inventory data into a more appropriate collection type that allows for fast lookups and updates by product ID.
# Current slow implementation
inventory = [
("PROD_001", 10),
("PROD_002", 5),
("PROD_003", 20),
# ... imagine thousands of products
]
def update_stock(product_id, amount):
for i in range(len(inventory)):
if inventory[i][0] == product_id:
# Update the stock value in the tuple (which is tricky since tuples are immutable)
current_stock = inventory[i][1]
inventory[i] = (product_id, current_stock + amount)
return True
return False
# Test the refactor
update_stock("PROD_002", 15)
print(inventory)
Modify the data structure and the function so that you no longer need a for loop to find the product.
There are no comments for now.