Lists
Table of Contents
Lists are Python’s most commonly used data structure. They’re ordered, mutable collections that can hold any mix of types. Whether you’re processing API responses, managing user data, or implementing algorithms, you’ll use lists constantly.
Creating Lists
# Empty list
items = []
# List with values
numbers = [1, 2, 3, 4, 5]
names = ["Alice", "Bob", "Charlie"]
mixed = [42, "hello", True, 3.14, None]
# From other iterables
letters = list("hello") # ['h', 'e', 'l', 'l', 'o']
nums = list(range(1, 6)) # [1, 2, 3, 4, 5]
Accessing Elements
Lists are zero-indexed. Use negative indices to count from the end:
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
print(fruits[0]) # apple
print(fruits[2]) # cherry
print(fruits[-1]) # elderberry (last item)
print(fruits[-2]) # date (second to last)
Slicing
Extract a portion of a list with [start:stop:step]:
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(nums[2:5]) # [2, 3, 4] — index 2 up to (not including) 5
print(nums[:3]) # [0, 1, 2] — from start
print(nums[7:]) # [7, 8, 9] — to end
print(nums[::2]) # [0, 2, 4, 6, 8] — every other element
print(nums[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] — reversed
Slicing always returns a new list — it doesn’t modify the original.
Modifying Lists
Adding elements
fruits = ["apple", "banana"]
fruits.append("cherry") # add to end → ['apple', 'banana', 'cherry']
fruits.insert(1, "blueberry") # insert at index → ['apple', 'blueberry', 'banana', 'cherry']
fruits.extend(["date", "fig"]) # add multiple → [..., 'date', 'fig']
Removing elements
fruits = ["apple", "banana", "cherry", "banana", "date"]
fruits.remove("banana") # removes FIRST occurrence → ['apple', 'cherry', 'banana', 'date']
popped = fruits.pop() # removes and returns last → 'date'
popped = fruits.pop(0) # removes and returns at index → 'apple'
del fruits[0] # delete by index
fruits.clear() # remove everything → []
Updating elements
colors = ["red", "green", "blue"]
colors[1] = "yellow" # ['red', 'yellow', 'blue']
colors[0:2] = ["pink", "purple"] # ['pink', 'purple', 'blue']
Common Operations
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(len(numbers)) # 8
print(min(numbers)) # 1
print(max(numbers)) # 9
print(sum(numbers)) # 31
print(numbers.count(1)) # 2 (how many times 1 appears)
print(numbers.index(5)) # 4 (index of first occurrence)
print(5 in numbers) # True
print(10 in numbers) # False
Sorting
numbers = [3, 1, 4, 1, 5, 9]
# Sort in place (modifies the list)
numbers.sort()
print(numbers) # [1, 1, 3, 4, 5, 9]
numbers.sort(reverse=True)
print(numbers) # [9, 5, 4, 3, 1, 1]
# sorted() returns a new list (original unchanged)
original = [3, 1, 4, 1, 5]
ordered = sorted(original)
print(original) # [3, 1, 4, 1, 5] — unchanged
print(ordered) # [1, 1, 3, 4, 5]
Sorting with a key function
words = ["banana", "apple", "cherry", "date"]
# Sort by length
words.sort(key=len)
print(words) # ['date', 'apple', 'banana', 'cherry']
# Sort objects by a property
users = [
{"name": "Charlie", "age": 30},
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 28}
]
users.sort(key=lambda u: u["age"])
print([u["name"] for u in users]) # ['Alice', 'Bob', 'Charlie']
Iterating
fruits = ["apple", "banana", "cherry"]
# Simple loop
for fruit in fruits:
print(fruit)
# With index
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# enumerate with custom start
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
List Comprehensions
A concise way to create lists by transforming or filtering another iterable:
# Basic: [expression for item in iterable]
squares = [x ** 2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# With condition: [expression for item in iterable if condition]
evens = [x for x in range(20) if x % 2 == 0]
print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# Transform and filter
names = ["Alice", "Bob", "Charlie", "Dave"]
long_upper = [name.upper() for name in names if len(name) > 3]
print(long_upper) # ['ALICE', 'CHARLIE', 'DAVE']
When to use comprehensions vs loops
# Comprehension — great for simple transformations
prices = [10, 25, 5, 40]
with_tax = [price * 1.08 for price in prices]
# Loop — better when logic is complex or has side effects
results = []
for price in prices:
if price > 20:
discounted = price * 0.9
results.append(round(discounted, 2))
If a comprehension needs more than one line to understand, use a loop instead.
Nested Lists (2D Lists)
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(matrix[0]) # [1, 2, 3] — first row
print(matrix[1][2]) # 6 — row 1, column 2
# Iterate over all elements
for row in matrix:
for cell in row:
print(cell, end=" ")
print()
# Flatten with a comprehension
flat = [cell for row in matrix for cell in row]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
Copying Lists
Assignment doesn’t copy — it creates another reference to the same list:
original = [1, 2, 3]
reference = original # NOT a copy!
reference.append(4)
print(original) # [1, 2, 3, 4] — modified!
# Shallow copy (works for simple lists)
copy1 = original.copy()
copy2 = original[:]
copy3 = list(original)
# Deep copy (needed for nested lists)
import copy
nested = [[1, 2], [3, 4]]
deep = copy.deepcopy(nested)
deep[0].append(99)
print(nested) # [[1, 2], [3, 4]] — unaffected
Unpacking
# Basic unpacking
first, second, third = [1, 2, 3]
# Star unpacking — collect the rest
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
# Swap values
a, b = 1, 2
a, b = b, a
print(a, b) # 2, 1
Useful Patterns
Filtering with conditions
scores = [85, 42, 91, 67, 73, 55, 88]
passing = [s for s in scores if s >= 70]
print(passing) # [85, 91, 67, 73, 88]
Zipping lists together
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
paired = list(zip(names, scores))
print(paired) # [('Alice', 95), ('Bob', 87), ('Charlie', 92)]
# Create a dict from two lists
score_map = dict(zip(names, scores))
print(score_map) # {'Alice': 95, 'Bob': 87, 'Charlie': 92}
Removing duplicates (preserving order)
items = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
unique = list(dict.fromkeys(items))
print(unique) # [3, 1, 4, 5, 9, 2, 6]
Chunking a list
def chunk(lst, size):
return [lst[i:i + size] for i in range(0, len(lst), size)]
print(chunk([1, 2, 3, 4, 5, 6, 7], 3))
# [[1, 2, 3], [4, 5, 6], [7]]
Performance Notes
| Operation | Time Complexity |
|---|---|
append() |
O(1) |
pop() (last) |
O(1) |
pop(0) (first) |
O(n) |
insert(0, x) |
O(n) |
x in list |
O(n) |
list[i] |
O(1) |
sort() |
O(n log n) |
If you need fast insertions/removals at both ends, use collections.deque instead. If you need fast membership testing, use a set.
What’s Next
Lists pair naturally with functions — most real Python programs pass lists into functions, transform them, and return new lists. From here, you can explore dictionaries (Python’s hash map) for key-value data, or dive into list comprehensions and generator expressions for more advanced data processing.