Skip to main content

Data Structures

Lists

List Creation

# Empty list
empty_list = []
empty_list = list()

# With initial values
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True]
nested = [[1, 2], [3, 4], [5, 6]]

# List constructor
from_string = list("hello") # ['h', 'e', 'l', 'l', 'o']
from_range = list(range(5)) # [0, 1, 2, 3, 4]
from_tuple = list((1, 2, 3)) # [1, 2, 3]

List Methods

fruits = ["apple", "banana", "orange"]

# Adding elements
fruits.append("grape") # Add to end
fruits.insert(1, "mango") # Insert at index 1
fruits.extend(["kiwi", "pear"]) # Add multiple items

# Removing elements
fruits.remove("banana") # Remove first occurrence
popped = fruits.pop() # Remove and return last item
popped = fruits.pop(0) # Remove and return at index 0
fruits.clear() # Remove all elements

# Searching and counting
index = fruits.index("apple") # Find index (raises ValueError if not found)
count = fruits.count("apple") # Count occurrences

# Sorting and reversing
fruits.sort() # Sort in place (ascending)
fruits.sort(reverse=True) # Sort in place (descending)
fruits.sort(key=len) # Sort by length
fruits.reverse() # Reverse in place

# Copying
shallow_copy = fruits.copy()
shallow_copy = fruits[:]
deep_copy = copy.deepcopy(fruits) # For nested structures

List Slicing

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Basic slicing
numbers[2:5] # [2, 3, 4] (start:stop)
numbers[:5] # [0, 1, 2, 3, 4] (from beginning)
numbers[5:] # [5, 6, 7, 8, 9] (to end)
numbers[:] # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (full copy)

# Step slicing
numbers[::2] # [0, 2, 4, 6, 8] (every 2nd element)
numbers[1::2] # [1, 3, 5, 7, 9] (every 2nd, starting at 1)
numbers[::-1] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] (reverse)

# Negative indexing
numbers[-1] # 9 (last element)
numbers[-3:] # [7, 8, 9] (last 3 elements)
numbers[:-3] # [0, 1, 2, 3, 4, 5, 6] (all except last 3)

List Operations

# Concatenation
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = list1 + list2 # [1, 2, 3, 4, 5, 6]

# Repetition
repeated = [0] * 5 # [0, 0, 0, 0, 0]
pattern = [1, 2] * 3 # [1, 2, 1, 2, 1, 2]

# Membership testing
2 in [1, 2, 3] # True
4 not in [1, 2, 3] # True

# Length and min/max
len([1, 2, 3]) # 3
min([1, 2, 3]) # 1
max([1, 2, 3]) # 3
sum([1, 2, 3]) # 6

Tuples

Tuple Creation

# Empty tuple
empty_tuple = ()
empty_tuple = tuple()

# With initial values
point = (3, 4)
person = ("Alice", 30, "Engineer")
single = (42,) # Single element tuple (note comma)
without_parentheses = 1, 2, 3 # Tuple packing

# Tuple constructor
from_list = tuple([1, 2, 3]) # (1, 2, 3)
from_string = tuple("hello") # ('h', 'e', 'l', 'l', 'o')
from_range = tuple(range(5)) # (0, 1, 2, 3, 4)

Tuple Operations

coordinates = (10, 20, 30)

# Indexing and slicing (same as lists)
coordinates[0] # 10
coordinates[-1] # 30
coordinates[1:] # (20, 30)

# Tuple methods
coordinates.count(10) # 1
coordinates.index(20) # 1

# Concatenation and repetition
(1, 2) + (3, 4) # (1, 2, 3, 4)
(1, 2) * 3 # (1, 2, 1, 2, 1, 2)

# Membership testing
20 in coordinates # True

Tuple Unpacking

# Basic unpacking
point = (3, 4)
x, y = point # x=3, y=4

# Multiple values
person = ("Alice", 30, "Engineer")
name, age, job = person

# Swapping variables
a, b = 1, 2
a, b = b, a # Swap: a=2, b=1

# Using underscore for ignored values
data = (1, 2, 3, 4, 5)
first, _, third, *rest = data # first=1, third=3, rest=[4, 5]

# Extended unpacking
first, *middle, last = data # first=1, middle=[2, 3, 4], last=5

Named Tuples

from collections import namedtuple

# Define named tuple
Point = namedtuple('Point', ['x', 'y'])
Person = namedtuple('Person', 'name age job') # Space-separated

# Create instances
p = Point(3, 4)
person = Person("Alice", 30, "Engineer")

# Access fields
print(p.x, p.y) # 3 4
print(person.name) # Alice

# Convert to dictionary
person_dict = person._asdict() # {'name': 'Alice', 'age': 30, 'job': 'Engineer'}

# Replace fields (returns new instance)
older_person = person._replace(age=31)

# Get field names
Person._fields # ('name', 'age', 'job')

Dictionaries

Dictionary Creation

# Empty dictionary
empty_dict = {}
empty_dict = dict()

# With initial values
person = {"name": "Alice", "age": 30, "city": "New York"}
person = dict(name="Alice", age=30, city="New York")

# From sequences
keys = ["name", "age", "city"]
values = ["Alice", 30, "New York"]
person = dict(zip(keys, values))

# Dictionary comprehension
squares = {x: x**2 for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# From list of tuples
person = dict([("name", "Alice"), ("age", 30)])

Dictionary Methods

person = {"name": "Alice", "age": 30, "city": "New York"}

# Accessing values
person["name"] # "Alice"
person.get("age") # 30
person.get("country", "Unknown") # "Unknown" (default value)

# Adding/updating
person["job"] = "Engineer" # Add new key
person["age"] = 31 # Update existing key
person.update({"salary": 50000, "department": "IT"})
person.update(zip(["bonus", "level"], [5000, "Senior"]))

# Removing
del person["city"] # Remove key (raises KeyError if not found)
age = person.pop("age") # Remove and return value
job = person.pop("job", "Unknown") # Remove with default
person.clear() # Remove all items

# Dictionary views
person.keys() # dict_keys(['name', 'age', 'city'])
person.values() # dict_values(['Alice', 30, 'New York'])
person.items() # dict_items([('name', 'Alice'), ...])

Dictionary Operations

# Membership testing
"name" in person # True (checks keys)
"Alice" in person.values() # True (checks values)

# Length
len(person) # Number of key-value pairs

# Copying
shallow_copy = person.copy()
shallow_copy = dict(person)

# Merging dictionaries (Python 3.9+)
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged = dict1 | dict2 # {"a": 1, "b": 2, "c": 3, "d": 4}

# Merging with update
dict1.update(dict2) # Modifies dict1

# Dictionary unpacking
merged = {**dict1, **dict2} # Creates new dictionary

Nested Dictionaries

# Creating nested dictionaries
company = {
"name": "TechCorp",
"employees": {
"engineering": {
"alice": {"role": "Senior Dev", "salary": 80000},
"bob": {"role": "Junior Dev", "salary": 60000}
},
"sales": {
"charlie": {"role": "Sales Rep", "salary": 70000}
}
}
}

# Accessing nested values
alice_salary = company["employees"]["engineering"]["alice"]["salary"]

# Safe access with get()
alice_role = company.get("employees", {}).get("engineering", {}).get("alice", {}).get("role")

# Using defaultdict for nested structures
from collections import defaultdict

nested_dict = defaultdict(lambda: defaultdict(dict))
nested_dict["level1"]["level2"]["level3"] = "value"

Dictionary Iteration

person = {"name": "Alice", "age": 30, "city": "New York"}

# Iterate over keys
for key in person:
print(key)

for key in person.keys():
print(key)

# Iterate over values
for value in person.values():
print(value)

# Iterate over key-value pairs
for key, value in person.items():
print(f"{key}: {value}")

# Dictionary comprehension with conditions
adults = {name: age for name, age in people.items() if age >= 18}

Sets

Set Creation

# Empty set
empty_set = set() # Note: {} creates empty dict, not set

# With initial values
numbers = {1, 2, 3, 4, 5}
mixed = {1, "hello", 3.14}

# Set constructor
from_list = set([1, 2, 3, 2, 1]) # {1, 2, 3} (duplicates removed)
from_string = set("hello") # {'h', 'e', 'l', 'o'} (duplicates removed)
from_tuple = set((1, 2, 3)) # {1, 2, 3}

# Set comprehension
squares = {x**2 for x in range(5)} # {0, 1, 4, 9, 16}
evens = {x for x in range(10) if x % 2 == 0} # {0, 2, 4, 6, 8}

Set Methods

fruits = {"apple", "banana", "orange"}

# Adding elements
fruits.add("grape") # Add single element
fruits.update(["kiwi", "pear"]) # Add multiple elements
fruits.update("mango") # Add characters: 'm', 'a', 'n', 'g', 'o'

# Removing elements
fruits.remove("banana") # Remove (raises KeyError if not found)
fruits.discard("banana") # Remove (no error if not found)
popped = fruits.pop() # Remove and return arbitrary element
fruits.clear() # Remove all elements

# Copying
fruits_copy = fruits.copy()
fruits_copy = set(fruits)

Set Operations

set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
set3 = {4, 5, 6, 7}

# Union (all elements)
union = set1 | set2 # {1, 2, 3, 4, 5, 6}
union = set1.union(set2)
union = set1.union(set2, set3) # Multiple sets

# Intersection (common elements)
intersection = set1 & set2 # {3, 4}
intersection = set1.intersection(set2)

# Difference (elements in first but not second)
difference = set1 - set2 # {1, 2}
difference = set1.difference(set2)

# Symmetric difference (elements in either but not both)
sym_diff = set1 ^ set2 # {1, 2, 5, 6}
sym_diff = set1.symmetric_difference(set2)

# Update operations (modify original set)
set1.update(set2) # set1 = set1 | set2
set1.intersection_update(set2) # set1 = set1 & set2
set1.difference_update(set2) # set1 = set1 - set2
set1.symmetric_difference_update(set2) # set1 = set1 ^ set2

Set Comparisons

set1 = {1, 2, 3}
set2 = {1, 2, 3, 4}
set3 = {1, 2, 3}

# Equality
set1 == set3 # True
set1 != set2 # True

# Subset and superset
set1.issubset(set2) # True (set1 <= set2)
set1 < set2 # True (proper subset)
set2.issuperset(set1) # True (set2 >= set1)
set2 > set1 # True (proper superset)

# Disjoint (no common elements)
set1.isdisjoint({4, 5, 6}) # True

Frozen Sets

# Immutable set
frozen = frozenset([1, 2, 3, 4])
frozen_from_set = frozenset({1, 2, 3})

# Can be used as dictionary keys
cache = {frozenset([1, 2, 3]): "result"}

# All set operations work, but no modification methods
union = frozen | {5, 6} # Returns new frozenset
# frozen.add(5) # AttributeError: no add method

List Comprehensions

Basic List Comprehensions

# Basic syntax: [expression for item in iterable]
squares = [x**2 for x in range(5)] # [0, 1, 4, 9, 16]
names = [name.upper() for name in ["alice", "bob", "charlie"]]

# With conditions
evens = [x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8]
positive = [x for x in [-2, -1, 0, 1, 2] if x > 0] # [1, 2]

# String operations
words = ["hello", "world", "python"]
lengths = [len(word) for word in words] # [5, 5, 6]
capitalized = [word.capitalize() for word in words]

# Working with functions
def square(x):
return x ** 2

squared = [square(x) for x in range(5)]

Advanced List Comprehensions

# Nested comprehensions
matrix = [[i + j for j in range(3)] for i in range(3)]
# [[0, 1, 2], [1, 2, 3], [2, 3, 4]]

# Flattening nested lists
nested = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [item for sublist in nested for item in sublist]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Multiple conditions
filtered = [x for x in range(20) if x % 2 == 0 if x % 3 == 0]
# [0, 6, 12, 18]

# Conditional expressions
result = [x if x > 0 else 0 for x in [-2, -1, 0, 1, 2]]
# [0, 0, 0, 1, 2]

# Working with dictionaries
person = {"name": "Alice", "age": 30, "city": "New York"}
uppercase_values = [str(v).upper() for v in person.values()]
key_lengths = [len(k) for k in person.keys()]

Dictionary and Set Comprehensions

# Dictionary comprehensions
squares_dict = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Filter dictionary
person = {"name": "Alice", "age": 30, "city": "New York", "job": "Engineer"}
strings_only = {k: v for k, v in person.items() if isinstance(v, str)}

# Transform keys/values
upper_keys = {k.upper(): v for k, v in person.items()}
str_values = {k: str(v) for k, v in person.items()}

# Set comprehensions
unique_lengths = {len(word) for word in ["hello", "world", "hi", "python"]}
# {2, 5, 6}

# Conditional set comprehension
vowels = {char for char in "hello world" if char in "aeiou"}
# {'e', 'o'}

Generator Expressions

Basic Generator Expressions

# Generator expression syntax: (expression for item in iterable)
squares_gen = (x**2 for x in range(5)) # Generator object
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1

# Memory efficient for large datasets
large_gen = (x for x in range(1000000)) # No memory allocation yet

# Convert to list when needed
squares_list = list(squares_gen)

# Use with sum, max, min, etc.
sum_of_squares = sum(x**2 for x in range(10))
max_length = max(len(word) for word in ["hello", "world", "python"])

Generator Functions

# Generator function with yield
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b

# Using generator
fib_gen = fibonacci(10)
for num in fib_gen:
print(num)

# Infinite generator
def infinite_counter(start=0):
while True:
yield start
start += 1

# Use with next() or in loop with break
counter = infinite_counter(10)
print(next(counter)) # 10
print(next(counter)) # 11

Generator Advantages

# Memory efficient - generates values on demand
def read_large_file(filename):
with open(filename, 'r') as f:
for line in f:
yield line.strip()

# Pipeline processing
def process_data(data):
for item in data:
if item.startswith('ERROR'):
yield item.upper()

# Chaining generators
log_lines = read_large_file('log.txt')
error_lines = process_data(log_lines)

# Lazy evaluation
filtered = (x for x in range(1000000) if x % 2 == 0)
first_ten = list(itertools.islice(filtered, 10))

Collections Module

defaultdict

from collections import defaultdict

# Auto-create missing keys with default values
dd = defaultdict(list)
dd['fruits'].append('apple')
dd['fruits'].append('banana')
# {'fruits': ['apple', 'banana']}

# Different default types
dd_int = defaultdict(int) # Default value: 0
dd_list = defaultdict(list) # Default value: []
dd_set = defaultdict(set) # Default value: set()

# Custom default factory
def default_value():
return "N/A"

dd_custom = defaultdict(default_value)
print(dd_custom['missing']) # "N/A"

# Grouping items
from collections import defaultdict
words = ['apple', 'banana', 'apricot', 'blueberry', 'cherry']
grouped = defaultdict(list)
for word in words:
grouped[word[0]].append(word)
# {'a': ['apple', 'apricot'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

Counter

from collections import Counter

# Count occurrences
text = "hello world"
char_count = Counter(text)
# Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})

# Count from list
fruits = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
fruit_count = Counter(fruits)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})

# Most common items
fruit_count.most_common(2) # [('apple', 3), ('banana', 2)]
fruit_count.most_common() # All items, most common first

# Counter operations
counter1 = Counter('abc')
counter2 = Counter('bcd')

counter1 + counter2 # Counter({'b': 2, 'c': 2, 'a': 1, 'd': 1})
counter1 - counter2 # Counter({'a': 1})
counter1 & counter2 # Counter({'b': 1, 'c': 1}) (intersection)
counter1 | counter2 # Counter({'a': 1, 'b': 1, 'c': 1, 'd': 1}) (union)

# Update counter
fruit_count.update(['apple', 'date'])
fruit_count.subtract(['apple', 'banana'])

deque (Double-ended queue)

from collections import deque

# Create deque
dq = deque([1, 2, 3])
dq = deque(maxlen=5) # Fixed-size deque

# Add elements
dq.append(4) # Add to right
dq.appendleft(0) # Add to left
dq.extend([5, 6]) # Add multiple to right
dq.extendleft([1,2]) # Add multiple to left

# Remove elements
right_item = dq.pop() # Remove from right
left_item = dq.popleft() # Remove from left

# Rotation
dq.rotate(1) # Rotate right
dq.rotate(-1) # Rotate left

# Other operations
dq.reverse() # Reverse deque
dq.clear() # Remove all elements
dq.count(1) # Count occurrences
dq.remove(2) # Remove first occurrence

# Use cases
# 1. Sliding window
def sliding_window(data, window_size):
window = deque(maxlen=window_size)
for item in data:
window.append(item)
if len(window) == window_size:
yield list(window)

# 2. Undo functionality
class UndoStack:
def __init__(self, maxlen=100):
self.history = deque(maxlen=maxlen)

def execute(self, action):
self.history.append(action)

def undo(self):
if self.history:
return self.history.pop()

OrderedDict

from collections import OrderedDict

# Maintains insertion order (Python 3.7+ dict does this too)
od = OrderedDict()
od['first'] = 1
od['second'] = 2
od['third'] = 3

# Move to end
od.move_to_end('first') # Move to end
od.move_to_end('second', last=False) # Move to beginning

# Pop items
last_item = od.popitem() # Remove and return last item
first_item = od.popitem(last=False) # Remove and return first item

# Reorder
od.move_to_end('key') # Move key to end

ChainMap

from collections import ChainMap

# Combine multiple mappings
defaults = {'color': 'red', 'user': 'guest'}
user_config = {'user': 'admin', 'theme': 'dark'}
cmd_args = {'color': 'blue'}

# Create chain (first mapping has priority)
config = ChainMap(cmd_args, user_config, defaults)
print(config['color']) # 'blue' (from cmd_args)
print(config['user']) # 'admin' (from user_config)
print(config['theme']) # 'dark' (from user_config)

# Add new mapping
config = config.new_child({'debug': True})

# Get underlying mappings
config.maps # List of all mappings

Advanced Operations

Memory-Efficient Techniques

# Use generators for large datasets
def process_large_file(filename):
with open(filename, 'r') as f:
for line in f:
yield line.strip().upper()

# Use itertools for efficient iteration
import itertools

# Chain multiple iterables
combined = itertools.chain([1, 2, 3], [4, 5, 6], [7, 8, 9])

# Infinite iterators
count = itertools.count(10, 2) # 10, 12, 14, 16, ...
cycle = itertools.cycle([1, 2, 3]) # 1, 2, 3, 1, 2, 3, ...

# Grouping
data = [1, 1, 2, 2, 2, 3, 3]
grouped = itertools.groupby(data)
for key, group in grouped:
print(key, list(group))

# Combinations and permutations
items = ['A', 'B', 'C']
perms = list(itertools.permutations(items, 2)) # [('A', 'B'), ('A', 'C'), ...]
combs = list(itertools.combinations(items, 2)) # [('A', 'B'), ('A', 'C'), ('B', 'C')]

Data Structure Conversion

# Convert between data structures
my_list = [1, 2, 3, 4, 5]
my_tuple = tuple(my_list) # (1, 2, 3, 4, 5)
my_set = set(my_list) # {1, 2, 3, 4, 5}

# Dictionary to list of tuples
my_dict = {'a': 1, 'b': 2, 'c': 3}
items_list = list(my_dict.items()) # [('a', 1), ('b', 2), ('c', 3)]
keys_list = list(my_dict.keys()) # ['a', 'b', 'c']
values_list = list(my_dict.values()) # [1, 2, 3]

# List of tuples to dictionary
pairs = [('a', 1), ('b', 2), ('c', 3)]
dict_from_pairs = dict(pairs) # {'a': 1, 'b': 2, 'c': 3}

# Zip lists to create dictionary
keys = ['name', 'age', 'city']
values = ['Alice', 30, 'New York']
person = dict(zip(keys, values))

Sorting and Filtering

# Sort with custom key
students = [('Alice', 85), ('Bob', 90), ('Charlie', 78)]
by_grade = sorted(students, key=lambda x: x[1]) # Sort by grade
by_name = sorted(students, key=lambda x: x[0]) # Sort by name

# Sort dictionary by value
scores = {'Alice': 85, 'Bob': 90, 'Charlie': 78}
sorted_scores = sorted(scores.items(), key=lambda x: x[1], reverse=True)

# Filter with custom conditions
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))

# Multiple criteria sorting
data = [('Alice', 'A', 85), ('Bob', 'B', 90), ('Charlie', 'A', 78)]
sorted_data = sorted(data, key=lambda x: (x[1], -x[2])) # Sort by grade, then score desc

Performance Considerations

Time Complexity

# List operations
# Access: O(1)
# Append: O(1) amortized
# Insert: O(n)
# Delete: O(n)
# Search: O(n)

# Dictionary operations
# Access: O(1) average
# Insert: O(1) average
# Delete: O(1) average
# Search: O(1) average

# Set operations
# Add: O(1) average
# Remove: O(1) average
# Membership: O(1) average
# Union/Intersection: O(len(s1) + len(s2))

Memory Usage Tips

# Use generators for large datasets
large_data = (x for x in range(1000000)) # Memory efficient

# Use slots for classes with many instances
class Point:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y

# Use array for homogeneous numeric data
import array
numbers = array.array('i', [1, 2, 3, 4, 5]) # More memory efficient than list

# Use sets for membership testing
large_list = list(range(1000000))
large_set = set(large_list)
# 'in' operation is O(1) for sets, O(n) for lists

Best Practices

# Use list comprehensions over loops
# Good
squares = [x**2 for x in range(10)]

# Less efficient
squares = []
for x in range(10):
squares.append(x**2)

# Use dict.get() with default values
# Good
value = my_dict.get('key', 'default')

# Less good
if 'key' in my_dict:
value = my_dict['key']
else:
value = 'default'

# Use enumerate for index and value
# Good
for i, item in enumerate(items):
print(f"{i}: {item}")

# Less good
for i in range(len(items)):
print(f"{i}: {items[i]}")

# Use zip for parallel iteration
# Good
for name, age in zip(names, ages):
print(f"{name} is {age} years old")

# Less good
for i in range(len(names)):
print(f"{names[i]} is {ages[i]} years old")

Common Patterns

Data Processing Patterns

# Group by key
from collections import defaultdict
data = [('A', 1), ('B', 2), ('A', 3), ('B', 4), ('A', 5)]
grouped = defaultdict(list)
for key, value in data:
grouped[key].append(value)
# {'A': [1, 3, 5], 'B': [2, 4]}

# Count occurrences
from collections import Counter
items = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
counts = Counter(items)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})

# Remove duplicates while preserving order
def remove_duplicates(items):
seen = set()
result = []
for item in items:
if item not in seen:
seen.add(item)
result.append(item)
return result

# Alternatively, use dict.fromkeys()
unique_items = list(dict.fromkeys(items))

Caching and Memoization

# Simple cache using dictionary
cache = {}
def expensive_function(n):
if n in cache:
return cache[n]
result = n ** 2 # Expensive operation
cache[n] = result
return result

# Using functools.lru_cache decorator
from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)

This cheatsheet covers the essential Python data structures and their operations. Master these concepts to write efficient and readable Python code.