Data Structures: Beginner
Friendster collapsed under its own success because it stored friend relationships in arrays and performed full scans to check connections, making the site grind to a halt as its user base grew. Redis chose hash maps as its core data structure, achieving constant-time key lookups that scale to millions of queries per second without a performance cliff. The data structure you choose in the first week of a project determines whether your application thrives or crashes under growth. This lesson teaches you how to match the right data structure to the right problem before it becomes a production crisis.
Lists: Ordered Collections
Store and retrieve ordered data
Create a list using square brackets []. Items are separated by commas. The order you specify is the order they are stored, and that order is preserved throughout the life of the list.
The append method adds an item to the end of the list. This is one of the most common operations you will perform. Lists grow dynamically - you do not need to specify a size upfront, and Python handles the memory management for you. This makes lists perfect for situations where you do not know in advance how many items you will have.
Slicing is a powerful feature that lets you extract portions of a list using the syntax list[start:stop:step]. The start index is included, but the stop index is excluded. If you omit start, it defaults to the beginning. If you omit stop, it goes to the end. The optional step parameter lets you skip elements.
> You have a list of five metrics [10, 20, 30, 40, 50] and need to extract the middle three values. Pick the slice that captures exactly those elements.
metrics = [10, 20, 30, 40, 50] print(metrics)
Essential List Methods
> Add an element to the end of a list, then remove and capture the last element. Pick the method that grows the list and the one that shrinks it while returning the removed value.
data = [10, 20, 30, 40] data.(50) last = data.() print(last) print(len(data))
List Performance Overview
Understanding performance helps you write efficient code. Lists excel at accessing items by index and appending to the end - both operations happen in O(1) constant time, meaning they are equally fast regardless of list size. However, searching for a specific value requires checking each item one by one, which becomes slow for large lists.
Inserting or removing from the beginning or middle of a list is slow because all subsequent elements must be shifted. If you frequently need to add or remove from both ends, consider using a deque from the collections module instead.
- Access by index: list[0]
- Append to end: list.append(x)
- Pop from end: list.pop()
- Get length: len(list)
- Search: x in list
- Insert at start: list.insert(0, x)
- Remove by value: list.remove(x)
- Insert in middle
Tuples: Immutable Sequences
Lock down data that must not change
Create a tuple using parentheses () or just commas. Access elements the same way as lists, using square bracket indexing. You can iterate over tuples, slice them, and use all the read-only operations that work on lists.
Tuples Instead of Lists?
- Items will be added/removed
- Order may change (sorting)
- Building results incrementally
- Data should never change
- Need dictionary keys
- Returning multiple values
Tuple Unpacking
Tuple unpacking is especially common when iterating over dictionary items or when working with functions that return multiple values. The enumerate function, for example, returns tuples of (index, value) that you typically unpack in a for loop.
> This code tries to change the first element of a tuple, but tuples are immutable and do not support item assignment.
TypeError: 'tuple' object does not support item assignment
When designing functions that return multiple values, tuples are the idiomatic choice. Functions like min(), max(), divmod(), and many standard library functions return tuples that you unpack at the call site.
Dicts: Key-Value Storage
Look up any value by key instantly
Create a dictionary using curly braces {} with key-value pairs separated by colons. Keys must be immutable (strings, numbers, or tuples), while values can be anything - including other dictionaries, lists, or custom objects.
Iterating Over Dictionaries
Modifying Dictionaries
Safe Key Access
Accessing a key that does not exist raises a KeyError. To handle missing keys gracefully, use the get() method, which returns None (or a default value you specify) instead of raising an error.
.get() when a key might not exist. It prevents crashes and makes your code more robust against unexpected data.Nested Dictionaries
The chained get() pattern is verbose but safe. Each get() returns an empty dictionary if the key is missing, allowing the chain to continue without raising an error. The final get() returns the default value if the entire path does not exist.
> You have a user dictionary with a nested "profile" containing "name". Pick the access method and fallback that safely navigates the nested structure.
user = {"profile": {"name": "Alice"}} result = user("profile", )("name", "unknown") print(result)
Sets: Unique Collections
Eliminate duplicates and test membership
Create a set using curly braces {} with elements (not key-value pairs) or the set() function. Important gotcha: {} alone creates an empty dictionary, not an empty set. Use set() for an empty set.
If you need both uniqueness and order, you can use a dictionary with None values (since Python 3.7+, dictionaries maintain insertion order), or use the dict.fromkeys() pattern which preserves first-occurrence order.
Fast Membership Testing
The killer feature of sets is O(1) membership testing. Checking if an element exists in a set takes the same time whether the set has 100 elements or 100 million. This makes sets absolutely essential for any operation involving "is this item in my collection?" - a question that comes up constantly in data processing.
- O(1) constant time
- Same speed for any size
- Uses hash table internally
- O(n) linear time
- Slower as list grows
- Checks each item sequentially
Set Operations
Union combines two sets, giving you all unique elements from both. Intersection finds elements that appear in both sets. Difference finds elements that are in the first set but not the second. These operations run in O(n) time, making them efficient for large datasets.
> Find how many viewers visited both product categories and how many visited at least one. Pick the set operator for each question.
a = {"alice", "bob", "charlie"} b = {"bob", "charlie", "diana"} overlap = a b combined = a b print(len(overlap)) print(len(combined))
> This code uses {} to create an empty set, but Python interprets {} as an empty dictionary. Calling .add() on a dict raises an AttributeError.
AttributeError: 'dict' object has no attribute 'add'
|Union - elements in either set&Intersection - elements in both sets-Difference - elements in first but not second^Symmetric difference - elements in either but not both
frozensets are the immutable equivalent of sets. They support all the same operations as sets but cannot be modified after creation, making them hashable and usable as dictionary keys.
Choosing the Right Type
Match any problem to its best structure
The Decision Framework
Real-World Scenarios
Data Pipeline Patterns
Common Mistakes
- Use set() for membership tests
- Use .get() for safe dict access
- Build new lists with comprehensions
- Use tuples for fixed data
- Search large lists with "in"
- Access dict keys with [] blindly
- Modify a list while looping it
- Use {} for an empty set
Lists for Membership Tests
- blocked = ["ip1", "ip2", ...]
- if ip in blocked: # Slow!
- Checks every item
- blocked = {"ip1", "ip2", ...}
- if ip in blocked: # Fast!
- Instant hash lookup
Empty Dict vs Empty Set
A common gotcha: {} creates an empty dictionary, not an empty set. To create an empty set, use set().
Modifying Tuples
KeyError on Missing Keys
The solution is defensive coding. Always use the .get() method when a key might be missing, or check for key existence with the in operator before accessing. The .get() method is usually cleaner because it returns a default value in a single expression.
Mutating During Iteration
The safest approach is to build a new list using a list comprehension, which is also more readable and often faster. If you must modify in place, iterate over a copy of the list using the slice notation [:] to create a shallow copy.
> You have a list [1, 2, 3, 4, 5] and need to remove the even numbers. Pick the approach that filters safely without mutating the list during iteration.
numbers = [1, 2, 3, 4, 5] result = print(result)
Shallow vs Deep Copy
> You are a junior data engineer at Airbnb writing a pipeline that ingests a nightly batch of booking events, deduplicates listing IDs, maps each host to their revenue, and stores immutable rate-tier boundaries that must never change mid-run.
Lists are ordered, mutable, and allow duplicates - use for sequencesTuples are ordered, immutable - use for fixed data and dict keysDicts map keys to values - use for lookups and structured dataSets store unique elements - use for deduplication and fast membershipset instead of list for "in" checks on large collections.get() for safe dictionary access when keys might be missingset(), not {} (which creates a dict)The right container for your data
- Category
- Python
- Difficulty
- beginner
- Duration
- 42 minutes
- Challenges
- 0 hands-on challenges
Topics covered: Lists: Ordered Collections, Tuples: Immutable Sequences, Dicts: Key-Value Storage, Sets: Unique Collections, Choosing the Right Type
Lesson Sections
- Lists: Ordered Collections (concepts: pyListCreate)
Lists are Python's workhorse data structure. They hold items in a specific order, allow duplicates, and can grow or shrink as needed. When you receive a batch of records from an API, process rows from a CSV file, or collect results from a database query, you typically work with lists. Lists are by far the most commonly used data structure in Python. What makes lists so versatile is their flexibility. They can hold any type of data - numbers, strings, other lists, dictionaries, or custom objects.
- Tuples: Immutable Sequences (concepts: pyTuples)
Tuples look similar to lists but have one critical difference: they cannot be changed after creation. Once you create a tuple, you cannot add, remove, or modify its elements. This immutability is not a limitation - it is a feature that makes your code safer and more predictable. Think about data that should never change: database connection parameters, geographic coordinates, RGB color values, or API response codes. If you accidentally modify such data, bugs can be extremely difficult to track d
- Dicts: Key-Value Storage (concepts: pyDictCreate)
Dictionaries are one of Python's most powerful and frequently used data structures. They store key-value pairs, allowing you to look up values by their keys instantly. Think of a dictionary like a real dictionary: you look up a word (key) to find its definition (value). The difference is that Python dictionaries can use almost any immutable type as a key, not just strings. In data engineering, dictionaries are absolutely everywhere. JSON responses from APIs are dictionaries. Configuration files
- Sets: Unique Collections (concepts: pySets)
Sets are unordered collections of unique elements. When you add a duplicate to a set, it simply ignores it - no error, no warning, just silent deduplication. This makes sets perfect for eliminating duplicates, tracking unique visitors, and performing mathematical set operations like unions and intersections. Unlike lists and tuples, sets do not maintain any particular order. The elements are stored based on their hash values, which optimizes for fast operations rather than sequence. This trade-o
- Choosing the Right Type (concepts: pyDataTypes)
Selecting the right data structure is one of the most important skills in programming. The choice affects code clarity, performance, and correctness. A well-chosen data structure makes code simpler and faster. A poorly chosen one leads to complex workarounds and performance problems. The good news is that choosing becomes intuitive with practice. After working with these four structures for a while, you will instinctively know which one fits each situation. Until then, use a systematic decision