Free GuideBeginner
40 min read
#python#data structures#lists#tuples#dictionaries#sets#comprehensions#collections

Data Structures

Comprehensive guide on Data Structures.

Data Structures

Overview#

Python provides several built-in data structures for storing and organizing collections of data: lists, tuples, dictionaries, and sets, plus specialized containers in the collections module. Each has different characteristics around mutability, ordering, performance, and intended use, which makes choosing the right one an important skill for writing clean, efficient code. This notebook covers all of the core structures in depth — how they work internally, their methods, when to use each one, comprehensions for building them concisely, and common pitfalls beginners run into.

1. Lists#

A list is an ordered, mutable collection that can hold items of any type, including a mix of types.

Python 3.12 (WASM)
fruits = ["apple", "banana", "cherry"] mixed = [1, "two", 3.0, True, [4, 5]] # Accessing elements print(fruits[0]) # apple print(fruits[-1]) # cherry (last item) # Slicing: list[start:stop:step] print(fruits[0:2]) # ['apple', 'banana'] print(fruits[::-1]) # ['cherry', 'banana', 'apple'] (reversed) print(fruits[::2]) # every second item # Modifying fruits.append("date") fruits.insert(1, "avocado") fruits.remove("banana") # removes first match by value fruits[0] = "apricot" # replace by index print(fruits)

Common list methods#

MethodDescription
append(x)Add item to the end
insert(i, x)Insert item at index i
remove(x)Remove first matching item (raises ValueError if absent)
pop(i)Remove and return item at index i (default: last)
sort(key=None, reverse=False)Sort in place
sorted(list)Return a new sorted list, original unchanged
reverse()Reverse in place
index(x)Find index of first match (raises ValueError if absent)
count(x)Count occurrences of x
extend(iterable)Append all items from another iterable
clear()Remove all items
copy()Shallow copy of the list
Python 3.12 (WASM)
numbers = [5, 3, 8, 1] numbers.sort() print(numbers) # [1, 3, 5, 8] print(numbers.pop()) # 8 print(numbers) # [1, 3, 5] # Sorting with a key function people = [("Alice", 30), ("Bob", 25), ("Cara", 35)] people.sort(key=lambda p: p[1]) print(people) # [('Bob', 25), ('Alice', 30), ('Cara', 35)]

How lists work internally#

A Python list is a dynamic array: it stores references to objects in a contiguous, resizable block of memory. This makes indexing (lst[i]) and appending to the end very fast — O(1) on average — while inserting or removing from the beginning of a list is slower — O(n) — because every following element has to shift.

Python 3.12 (WASM)
# Fast: append to the end lst = [] for i in range(1000): lst.append(i) # O(1) amortized per call # Slow for large lists: inserting at the front shifts everything lst.insert(0, -1) # O(n)

Shallow copy vs deep copy#

Python 3.12 (WASM)
original = [[1, 2], [3, 4]] shallow = original.copy() # or original[:] shallow[0].append(99) print(original) # [[1, 2, 99], [3, 4]] — inner list is shared! import copy deep = copy.deepcopy(original) deep[0].append(100) print(original) # unaffected

A shallow copy duplicates the outer list, but nested mutable objects (like inner lists) are still shared references. Use copy.deepcopy() when you need a fully independent copy of nested structures.

2. Tuples#

A tuple is like a list, but immutable — once created, its contents cannot be reassigned. Tuples are typically used for fixed collections of related values, or when you want to guarantee data won't be modified.

Python 3.12 (WASM)
point = (3, 4) person = ("Alice", 30, "Engineer") single = (5,) # note the trailing comma — required for a 1-item tuple x, y = point # unpacking print(x, y) # 3 4 name, age, *rest = ("Bob", 28, "Engineer", "Remote") print(name, age, rest) # Bob 28 ['Engineer', 'Remote'] # Tuples cannot be modified # point[0] = 10 # TypeError: 'tuple' object does not support item assignment

Why use a tuple over a list?#

  • Signals intent that the data should not change — self-documenting code.
  • Slightly faster to create and more memory-efficient than lists.
  • Hashable (as long as all elements are hashable), so tuples can be used as dictionary keys or set members — lists cannot, since they're mutable.
  • Commonly used as lightweight, unnamed records: a coordinate, an RGB triple, a database row.
Python 3.12 (WASM)
locations = { (28.6139, 77.2090): "Delhi", (19.0760, 72.8777): "Mumbai", } print(locations[(28.6139, 77.2090)]) # Delhi

namedtuple for readable tuples#

When a tuple represents a record with meaningful fields, namedtuple gives you attribute access without the overhead of a full class.

Python 3.12 (WASM)
from collections import namedtuple Point = namedtuple("Point", ["x", "y"]) p = Point(3, 4) print(p.x, p.y) # 3 4 print(p) # Point(x=3, y=4)

3. Dictionaries#

A dictionary stores key-value pairs. Keys must be unique and hashable (strings, numbers, tuples of hashable items); values can be anything.

Python 3.12 (WASM)
student = { "name": "Rohan", "age": 21, "courses": ["Math", "Physics"] } print(student["name"]) # Rohan student["age"] = 22 # update student["email"] = "r@x.com" # add new key # Safe access with default (no KeyError) print(student.get("grade", "N/A")) # N/A # Removing keys del student["email"] popped_value = student.pop("age")

Common dictionary methods#

MethodDescription
keys()View of all keys
values()View of all values
items()View of key-value pairs
get(k, default)Safe lookup with fallback
pop(k, default)Remove key and return its value
setdefault(k, default)Return value if key exists, else set and return default
update(other)Merge another dict (or iterable of pairs) in
clear()Remove all items
Python 3.12 (WASM)
for key, value in student.items(): print(f"{key}: {value}") # setdefault avoids a separate existence check counts = {} for word in ["a", "b", "a", "c", "b", "a"]: counts[word] = counts.get(word, 0) + 1 print(counts) # {'a': 3, 'b': 2, 'c': 1}

defaultdict and Counter#

The collections module offers specialized dictionary variants that simplify common patterns.

Python 3.12 (WASM)
from collections import defaultdict, Counter # defaultdict: auto-initializes missing keys groups = defaultdict(list) for word in ["apple", "banana", "avocado", "blueberry"]: groups[word[0]].append(word) print(dict(groups)) # {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry']} # Counter: purpose-built for counting counts = Counter(["a", "b", "a", "c", "b", "a"]) print(counts) # Counter({'a': 3, 'b': 2, 'c': 1}) print(counts.most_common(2)) # [('a', 3), ('b', 2)]

Dictionary ordering#

Since Python 3.7, dictionaries preserve insertion order as a language guarantee, not just an implementation detail. This means iterating over .items() will always reflect the order keys were added.

4. Sets#

A set is an unordered collection of unique, hashable items. Sets are optimized for fast membership testing and eliminating duplicates.

Python 3.12 (WASM)
colors = {"red", "green", "blue"} colors.add("yellow") colors.discard("red") # no error if missing # colors.remove("red") # would raise KeyError if missing print("green" in colors) # True — O(1) average lookup # Deduplicate a list while losing order numbers = [1, 2, 2, 3, 3, 3] unique = set(numbers) print(unique) # {1, 2, 3} # Deduplicate while PRESERVING order (Python 3.7+ dict trick) ordered_unique = list(dict.fromkeys(numbers)) print(ordered_unique) # [1, 2, 3]

Set operations#

Python 3.12 (WASM)
a = {1, 2, 3} b = {2, 3, 4} print(a | b) # union: {1, 2, 3, 4} print(a & b) # intersection: {2, 3} print(a - b) # difference: {1} print(a ^ b) # symmetric difference: {1, 4} print(a.issubset({1, 2, 3, 4})) # True print(a.issuperset({1, 2})) # True

frozenset#

A frozenset is an immutable version of a set — hashable, so it can be used as a dictionary key or nested inside another set.

Python 3.12 (WASM)
fs = frozenset([1, 2, 3]) # fs.add(4) # AttributeError: frozenset has no add method

5. Choosing the Right Structure#

StructureOrderedMutableDuplicatesLookup byTypical use
ListYesYesYesIndexGeneral-purpose sequence
TupleYesNoYesIndexFixed, related values
DictionaryYes (3.7+)YesKeys uniqueKeyKey-based lookup
SetNoYesNoValue (membership)Membership tests, dedup
frozensetNoNoNoValueImmutable set, hashable

6. Comprehensions#

Comprehensions provide a concise, often faster syntax for building lists, dictionaries, and sets from an iterable, replacing verbose manual loops.

List comprehension#

Python 3.12 (WASM)
squares = [n ** 2 for n in range(10)] evens = [n for n in range(20) if n % 2 == 0] # Nested comprehension: flatten a list of lists matrix = [[1, 2], [3, 4], [5, 6]] flat = [n for row in matrix for n in row] print(flat) # [1, 2, 3, 4, 5, 6]

Dictionary comprehension#

Python 3.12 (WASM)
word_lengths = {word: len(word) for word in ["apple", "kiwi", "banana"]} # {'apple': 5, 'kiwi': 4, 'banana': 6} # Invert a dictionary original = {"a": 1, "b": 2} inverted = {v: k for k, v in original.items()} # {1: 'a', 2: 'b'}

Set comprehension#

Python 3.12 (WASM)
unique_lengths = {len(word) for word in ["apple", "kiwi", "banana"]} # {5, 4, 6}

Generator expressions#

A generator expression looks like a list comprehension but with parentheses, and produces items lazily — one at a time — instead of building the entire collection in memory at once.

Python 3.12 (WASM)
total = sum(n ** 2 for n in range(1_000_000)) # never builds a full list

When to prefer a comprehension vs a loop#

Python 3.12 (WASM)
# Instead of: result = [] for n in range(10): if n % 2 == 0: result.append(n * n) # Prefer: result = [n * n for n in range(10) if n % 2 == 0]

Comprehensions should stay readable — if a comprehension needs multiple nested loops or complex conditions, a regular for loop with clear variable names is often the better, more maintainable choice.

7. Mutability and Identity Pitfalls#

The mutable default argument trap#

Python 3.12 (WASM)
# Dangerous: the same list is reused across all calls! def add_item(item, items=[]): items.append(item) return items print(add_item(1)) # [1] print(add_item(2)) # [1, 2] <- unexpected! # Correct approach def add_item(item, items=None): if items is None: items = [] items.append(item) return items

Equality vs identity#

Python 3.12 (WASM)
a = [1, 2, 3] b = [1, 2, 3] print(a == b) # True (same contents) print(a is b) # False (different objects in memory) c = a print(a is c) # True (same object)

Notes#

  • Lists and dictionaries are the two most commonly used structures in everyday Python code.
  • Use a tuple when data should not change after creation, or when you need a hashable, fixed-size grouping.
  • Use a set when you only care about uniqueness and membership, not order or duplicates.
  • collections.defaultdict and collections.Counter solve extremely common patterns (grouping, counting) more cleanly than manual dict logic.
  • Generator expressions are preferable to list comprehensions when you only need to iterate once and don't need the full collection in memory.
  • Be cautious with mutable default arguments (def f(x=[])) — they are created once, at function definition time, and shared across all calls.

Best Practices Checklist#

  • Use lists for ordered, changeable collections
  • Use tuples for fixed, related groups of values
  • Use dictionaries for key-based lookups
  • Use sets when uniqueness and fast membership testing matter
  • Prefer comprehensions over manual loops for simple transformations
  • Use generator expressions instead of list comprehensions for one-time iteration over large data
  • Reach for defaultdict/Counter instead of manual dictionary bookkeeping
  • Use .get() or .setdefault() instead of direct key access when a key might be missing
  • Avoid mutable default arguments in function definitions
  • Use copy.deepcopy() when nested mutable structures must be fully independent
  • Remember tuples (and frozensets) can be dictionary keys, but lists and sets cannot
Knowledge Checkpoint

Core Data Structures Checkpoint

Q1.What is the average time complexity of item lookup (`key in dict`) in a Python dictionary?
AO(1)
BO(n)
CO(log n)
DO(n log n)
Q2.Why cannot a Python `list` be used as a key in a standard `dict` or element in a `set`?
ALists are too large to fit in hash buckets.
BLists are mutable and do not implement a stable `__hash__()` method (they are unhashable).
CPython dicts only support string keys.
DLists require continuous integer indices.
Q3.What is the result of the dictionary comprehension `{x: x**2 for x in range(3)}`?
A[0, 1, 4]
B{0, 1, 4}
C{0: 0, 1: 1, 2: 4}
D(0, 1, 4)
Track Your Learning

Finished studying this notebook?

Mark this guide as completed to update your course progress roadmap.