Python Data Structures
Explore tuples, sets, deques, and learn how to choose the right data structure for each problem.
Tuples: Immutable Sequences
A tuple is an ordered, immutable sequence. Once created, its elements cannot be changed, added, or removed. Tuples are ideal for fixed collections like coordinates, RGB colours, or database records.
# Creating tuples
coordinates = (3, 7)
rgb_red = (255, 0, 0)
single = (42,) # comma needed for single-element tuple
# Tuple unpacking
x, y = coordinates
print(f"x={x}, y={y}") # x=3, y=7
# Named tuples for readable code
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(10, 20)
print(p.x, p.y) # 10 20
# Tuples as dictionary keys (lists cannot be)
locations = {(0, 0): "Origin", (1, 2): "Point A"}
print(locations[(0, 0)]) # Origin
Tuples use less memory than lists and are faster to iterate. Use them when data should not change.
Sets: Unique Collections
A set is an unordered collection of unique elements. Sets excel at membership testing, removing duplicates, and performing mathematical set operations (union, intersection, difference).
# Creating and using sets
fruits = {"apple", "banana", "cherry", "apple"}
print(fruits) # {'apple', 'banana', 'cherry'} - no duplicates
# Fast membership testing: O(1) average
print("banana" in fruits) # True
# Set operations
a = {1, 2, 3, 4, 5}
b = {4, 5, 6, 7, 8}
print(a | b) # Union: {1, 2, 3, 4, 5, 6, 7, 8}
print(a & b) # Intersection: {4, 5}
print(a - b) # Difference: {1, 2, 3}
print(a ^ b) # Symmetric: {1, 2, 3, 6, 7, 8}
# Remove duplicates from a list
names = ["Alice", "Bob", "Alice", "Charlie", "Bob"]
unique_names = list(set(names))
print(unique_names) # ['Alice', 'Bob', 'Charlie']
Deques and Choosing the Right Structure
A deque (double-ended queue) from collections allows efficient append and pop from both ends. Lists are slow at inserting or removing from the front (O(n)), but deques do this in O(1).
from collections import deque
# Create a deque
queue = deque(["task1", "task2", "task3"])
# Efficient operations on both ends
queue.append("task4") # add to right
queue.appendleft("task0") # add to left
print(queue) # deque(['task0', 'task1', 'task2', 'task3', 'task4'])
queue.pop() # remove from right -> 'task4'
queue.popleft() # remove from left -> 'task0'
# Fixed-size deque (keeps last N items)
recent = deque(maxlen=3)
for i in range(5):
recent.append(i)
print(recent) # deque([2, 3, 4], maxlen=3)
When to Use Each Structure
| Structure | Best For | Mutable? |
|---|---|---|
| list | Ordered, changeable collections | Yes |
| tuple | Fixed data, dictionary keys, function returns | No |
| set | Unique items, fast membership checks | Yes |
| dict | Key-value lookups, structured data | Yes |
| deque | Queues, stacks, sliding windows | Yes |
Key Vocabulary
Immutable
An object whose state cannot be changed after creation. Tuples and strings are immutable.
Hash Table
The underlying data structure for sets and dicts, enabling O(1) average lookups.
Deque
A double-ended queue that supports efficient appends and pops from both ends.
Time Complexity
A measure of how an operation's speed scales with input size, expressed as Big-O notation (e.g., O(1), O(n)).
Worked Examples
Find common elements between two lists using sets
def find_common(list_a, list_b):
"""Return sorted list of elements in both lists."""
set_a = set(list_a)
set_b = set(list_b)
return sorted(set_a & set_b) # intersection
subjects_alice = ["Maths", "English", "Science", "Art"]
subjects_bob = ["Science", "Music", "Maths", "PE"]
common = find_common(subjects_alice, subjects_bob)
print(common) # ['Maths', 'Science']
Why sets? Converting to sets makes the intersection O(min(n, m)) instead of O(n * m) with nested loops.
Implement a browser history using a deque
from collections import deque
class BrowserHistory:
def __init__(self, max_history=50):
self.history = deque(maxlen=max_history)
self.current = None
def visit(self, url):
if self.current:
self.history.append(self.current)
self.current = url
def back(self):
if self.history:
self.current = self.history.pop()
return self.current
browser = BrowserHistory()
browser.visit("google.com")
browser.visit("github.com")
browser.visit("python.org")
print(browser.back()) # github.com
print(browser.back()) # google.com
maxlen ensures the history never exceeds 50 entries, automatically dropping the oldest when full.
Use named tuples for structured records
from collections import namedtuple
Student = namedtuple("Student", ["name", "year", "gpa"])
students = [
Student("Alice", 12, 3.9),
Student("Bob", 12, 3.5),
Student("Charlie", 12, 3.8),
]
# Sort by GPA descending
top_students = sorted(students, key=lambda s: s.gpa, reverse=True)
for s in top_students:
print(f"{s.name}: GPA {s.gpa}")
# Alice: GPA 3.9
# Charlie: GPA 3.8
# Bob: GPA 3.5
# Access by name (more readable than index)
print(top_students[0].name) # Alice
Named tuples give you the immutability of tuples with the readability of named attributes. They are lighter than full classes.
Knowledge Check
Select the correct answer for each question. Click "Check Answer" to see if you are right.
Question 1
What happens when you try to modify an element in a tuple?
Question 2
What does {1, 2, 3} & {2, 3, 4} return?
Question 3
Why would you use a deque instead of a list for a queue?
Question 4
Which data structure would you choose to store unique student IDs for fast lookup?
Question 5
How do you create a single-element tuple in Python?
Key Concepts Summary
- ●Tuples are immutable sequences ideal for fixed data and can serve as dictionary keys.
- ●Sets store unique elements and support fast O(1) membership testing plus set operations.
- ●Deques provide O(1) append/pop from both ends, making them ideal for queues and stacks.
- ●Choose the right data structure based on whether you need ordering, uniqueness, mutability, or speed.
- ●Named tuples add readability to tuples by providing attribute access by name.