Implement Simple Data Structures in Python
Learn to implement simple data structures in Python with this hands-on tutorial — covers lists, stacks, queues, and key operations step by step.
Focus: implement simple data structures in python
When you start writing Python, you quickly realize that choosing the right way to organize your data is everything. Without a solid grasp of implement simple data structures in Python, your code can become messy, slow, and hard to debug. This lesson equips you with the fundamental building blocks—lists, stacks, queues, and sets—so you can store and manipulate data with confidence, whether you're building a simple script or a complex application.
The problem this lesson solves
Imagine you're processing a stream of user requests. You need to process them in the order they arrive (first-in, first-out), but you accidentally use a plain list. Suddenly, you're shifting elements around with pop(0), which is O(n) and grinds your program to a halt. Or you're tracking unique visitors, but your code is cluttered with if x not in list checks. These are exactly the problems that implement simple data structures in Python solves. By learning the right data structure for each scenario, you avoid performance pitfalls and write cleaner, more intuitive code.
Core concept / mental model
Think of data structures as different containers in your kitchen:
- A list is like a grocery list—an ordered collection you can add to, remove from, and reorder. You can access items by their position (index).
- A stack is like a stack of plates. The last plate you put on top is the first one you take off (Last-In, First-Out — LIFO). Great for undo operations or backtracking.
- A queue is like a line at a coffee shop. The first person to line up gets served first (First-In, First-Out — FIFO). Perfect for task scheduling.
- A set is like a bag of unique items. You can't have duplicates, and you can check membership (is this flavor of coffee available?) very quickly.
In Python, you can implement simple data structures in Python using built-in types (list, set, deque from collections) or by writing your own classes for clarity and extra methods.
How it works step by step
1. List — the Swiss Army knife
A Python list is a dynamic array. You can:
- Append to the end (my_list.append(item))
- Insert at an index (my_list.insert(i, item))
- Remove by value (my_list.remove(item)) or by index (del my_list[i] or my_list.pop(i))
- Access by index (my_list[i])
Lists are ordered, mutable, and allow duplicates. They are the most versatile but can be slow for insertions/deletions at the front.
2. Stack — LIFO behavior
At its core, a stack needs two operations: push (add item) and pop (remove and return the most recently added item). Python's list can act as a stack directly:
- push is append
- pop is pop() (no index)
But you can also encapsulate this in a class for clarity.
3. Queue — FIFO behavior
A queue needs enqueue (add item to the back) and dequeue (remove item from the front). Using a list with pop(0) is O(n), which is slow for large queues. Instead, use collections.deque, which offers O(1) appends and pops from both ends.
4. Set — unique membership
A set is an unordered collection of unique elements. Use it for fast membership tests (if item in my_set) or eliminating duplicates. No indexing is possible.
Hands-on walkthrough
Let's implement simple data structures in Python from scratch and using built-in tools.
Example 1: Stack as a class
class Stack:
def __init__(self):
self._items = []
def push(self, item):
"""Add item to the top."""
self._items.append(item)
def pop(self):
"""Remove and return the top item. Raises IndexError if empty."""
if not self._items:
raise IndexError("pop from empty stack")
return self._items.pop()
def is_empty(self):
return len(self._items) == 0
# Usage
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.pop()) # Output: 3
print(stack.pop()) # Output: 2
print(stack.is_empty()) # Output: False
stack.pop()
print(stack.is_empty()) # Output: True
Example 2: Queue using deque
from collections import deque
class Queue:
def __init__(self):
self._items = deque()
def enqueue(self, item):
"""Add item to the back."""
self._items.append(item)
def dequeue(self):
"""Remove and return the front item."""
if not self._items:
raise IndexError("dequeue from empty queue")
return self._items.popleft()
def is_empty(self):
return len(self._items) == 0
# Usage
q = Queue()
q.enqueue('a')
q.enqueue('b')
q.enqueue('c')
print(q.dequeue()) # Output: a
print(q.dequeue()) # Output: b
print(q.is_empty()) # Output: False
q.dequeue()
print(q.is_empty()) # Output: True
Example 3: Set for unique elements
def unique_names(names_list):
"""Return a list of unique names, preserving first occurrence order."""
seen = set()
unique = []
for name in names_list:
if name not in seen:
seen.add(name)
unique.append(name)
return unique
names = ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob']
print(unique_names(names)) # Output: ['Alice', 'Bob', 'Charlie']
# Using set directly (order not guaranteed)
print(set(names)) # Output: {'Charlie', 'Bob', 'Alice'} (order may vary)
Example 4: List as a simple stack (built-in)
# Python list already works as a stack
stack = []
stack.append(1) # push
stack.append(2)
stack.append(3)
top = stack.pop() # pop
print(top) # Output: 3
print(stack) # Output: [1, 2]
Pro tip: For a stack that you want to limit in size, you can inherit from
listand overrideappendto checklen()before adding.
Compare options / when to choose what
| Data Structure | Use Case | Python Implementation | Time Complexity (average) |
|---|---|---|---|
| List | Ordered collection, frequent indexing, small front operations | list |
Append O(1), Insert at front O(n), Access O(1) |
| Stack | Undo/redo, parsing, backtracking | list or custom class |
Push O(1), Pop O(1) |
| Queue | Task scheduling, BFS, buffering | collections.deque |
Enqueue O(1), Dequeue O(1) |
| Set | Duplicate removal, membership testing | set |
Add O(1), Check membership O(1) |
- Choose a list when you need fast indexing and order matters.
- Choose a stack when you need LIFO access (e.g., undo functionality).
- Choose a queue when you need FIFO access (e.g., print queue).
- Choose a set when uniqueness or lookups are the primary concern.
Troubleshooting & edge cases
- Empty structure: Always check if the stack/queue/set is empty before popping/dequeuing. Custom classes should raise
IndexErroror returnNone. - Mutability: Sets can only contain hashable (immutable) elements. You cannot add a list to a set. Use tuples or frozensets instead.
- Performance vs readability: For small datasets, a list used as a queue (using
pop(0)) is fine, but for thousands of items,dequeis vastly faster. Always profile if performance is critical. - Preserving order in sets: Standard
setdoes not maintain insertion order. In Python 3.7+, dicts preserve order, butsetstill does not guarantee order. For ordered unique collections, usedict.fromkeys()orcollections.OrderedDict.
What you learned & what's next
You now understand how to implement simple data structures in Python—including lists, stacks, queues, and sets. You can build custom classes to encapsulate behavior, you know when to use deque for queues, and you recognize the trade-offs between different structures. These fundamentals are the bedrock of efficient Python programming.
Next, you'll explore recursion, where stacks naturally come into play for tracing function calls. Mastering stacks will give you a huge advantage in understanding how recursion works internally.
Practice recap
Practice exercise: Build a task scheduler that uses a queue to manage tasks (represented as strings) and a stack to undo the last completed task. Your scheduler should have add_task(task), complete_task() (adds to undo stack), and undo() (re-adds the last completed task to the queue). Testing with a few sample tasks will solidify your understanding of FIFO and LIFO simultaneously.
Common mistakes
- Using a list as a queue with
pop(0)— this is O(n) because every element shifts. Usecollections.dequeinstead. - Forgetting to check if a stack or queue is empty before popping, causing
IndexErrorin custom classes. - Trying to add a list to a set — sets require hashable elements. Use tuples instead of lists for mutable-like behavior.
- Assuming Python's built-in
setpreserves insertion order. It does not; usedict.fromkeys()for ordered unique collections.
Variations
- Use
collections.dequeas a drop-in stack as well, not just a queue — it's a double-ended queue. - Implement a doubly linked list for an ordered collection that supports O(1) insertions at both ends, though Python lists are often sufficient.
- Replace custom stack/queue classes with simple functions (e.g., using
list.append/list.pop) for small scripts to reduce boilerplate.
Real-world use cases
- Browser back button: use a stack to store visited URLs—pushing new pages, popping on back.
- Printer job queue: use a queue (FIFO) to process documents in the order they are sent to the printer.
- Unique user IDs from a feed: use a set to quickly filter out duplicate entries without manual checks.
Key takeaways
- Python's list is versatile but slow for front insertions/deletions — use deque for FIFO queues.
- A stack is simply a list with
appendfor push andpop()for pop — O(1) each. - For unique items and fast membership testing, sets are the go-to (O(1) average).
- Encapsulating data structures in a class improves readability and prevents misuse.
- Always consider immutability: sets require hashable elements for membership.
- Choosing the right data structure is a critical optimization step — profile when in doubt.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.