Python Lists

Master Python lists: creating, modifying, slicing, sorting, list comprehensions, nested lists, and safe copying.

Beginner 12 min read 12 examples

Creating Lists

Lists are ordered, mutable sequences that can hold any mix of data types. They are defined with square brackets.

Python
# Empty list
empty = []
empty2 = list()

# List of numbers
numbers = [1, 2, 3, 4, 5]

# Mixed types
mixed = [42, "hello", 3.14, True, None]

# List from another iterable
chars   = list("Python")        # ['P', 'y', 't', 'h', 'o', 'n']
nums    = list(range(1, 6))     # [1, 2, 3, 4, 5]
doubled = list(range(0, 20, 2)) # [0, 2, 4, 6, ..., 18]

# Length and membership
fruits = ["apple", "banana", "cherry"]
print(len(fruits))          # 3
print("banana" in fruits)   # True
print("grape" not in fruits) # True

# Indexing (0-based, negative from end)
print(fruits[0])    # apple
print(fruits[-1])   # cherry
print(fruits[-2])   # banana

# Lists are mutable - items can be changed
fruits[1] = "blueberry"
print(fruits)   # ['apple', 'blueberry', 'cherry']

# Iterating
for fruit in fruits:
    print(fruit)

# With index using enumerate()
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")
# 0: apple
# 1: blueberry
# 2: cherry

List Methods

Python
items = [3, 1, 4, 1, 5, 9, 2, 6]

# Adding items
items.append(7)         # add to end:          [..., 6, 7]
items.insert(0, 0)      # insert at index 0:   [0, 3, 1, ...]
items.extend([8, 9])    # add all from iterable (like += )

# items += [10, 11]     # same as extend

# Removing items
items.remove(1)         # remove first occurrence of value 1
popped = items.pop()    # remove and return last item
popped2 = items.pop(0)  # remove and return item at index 0
del items[2]            # delete item at index 2 (no return value)
del items[1:3]          # delete a slice

# Search
nums = [10, 20, 30, 20, 40]
print(nums.index(20))   # 1  - index of first occurrence
print(nums.count(20))   # 2  - how many times value appears

# Reversing
nums.reverse()          # in place: [40, 20, 30, 20, 10]
print(list(reversed(nums)))  # returns iterator, convert to list

# Clearing
backup = nums.copy()
nums.clear()            # empties the list: []
print(nums)             # []
print(backup)           # [40, 20, 30, 20, 10]  (not affected)

# Concatenation and repetition (create new lists)
a = [1, 2, 3]
b = [4, 5, 6]
c = a + b               # [1, 2, 3, 4, 5, 6]  (new list)
d = a * 3               # [1, 2, 3, 1, 2, 3, 1, 2, 3]

# Unpacking into variables
first, *rest = [1, 2, 3, 4, 5]
print(first)    # 1
print(rest)     # [2, 3, 4, 5]

head, *middle, tail = [1, 2, 3, 4, 5]
print(head, middle, tail)   # 1 [2, 3, 4] 5
MethodDescriptionReturns
append(x)Add item to endNone
insert(i, x)Insert at index iNone
extend(iter)Add all items from iterableNone
remove(x)Remove first x (ValueError if missing)None
pop(i=-1)Remove and return item at iitem
index(x)Index of first xint
count(x)Count occurrences of xint
sort()Sort in placeNone
reverse()Reverse in placeNone
copy()Shallow copylist
clear()Remove all itemsNone

Slicing

List slicing uses the same [start:stop:step] syntax as strings. Slices always return a new list.

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

print(nums[2:5])    # [2, 3, 4]
print(nums[:3])     # [0, 1, 2]      - first 3
print(nums[-3:])    # [7, 8, 9]      - last 3
print(nums[::2])    # [0, 2, 4, 6, 8] - every 2nd
print(nums[::-1])   # [9, 8, 7, ..., 0] - reversed

# Slice assignment - replace a slice
nums[2:5] = [20, 30, 40]
print(nums)   # [0, 1, 20, 30, 40, 5, 6, 7, 8, 9]

# Insert without replacing (empty slice assignment)
nums[2:2] = [100, 200]
print(nums)   # [0, 1, 100, 200, 20, 30, 40, 5, 6, 7, 8, 9]

# Delete a slice
del nums[2:4]
print(nums)   # [0, 1, 20, 30, 40, 5, 6, 7, 8, 9]

# Shallow copy via slice
original = [1, 2, 3, 4, 5]
copy     = original[:]   # same as original.copy()
copy[0]  = 99
print(original)  # [1, 2, 3, 4, 5]  - not affected
print(copy)      # [99, 2, 3, 4, 5]

Sorting Lists

Python
nums = [3, 1, 4, 1, 5, 9, 2, 6]

# sort() modifies in place, returns None
nums.sort()
print(nums)             # [1, 1, 2, 3, 4, 5, 6, 9]

nums.sort(reverse=True)
print(nums)             # [9, 6, 5, 4, 3, 2, 1, 1]

# sorted() returns a new list, original unchanged
original = [3, 1, 4, 1, 5]
new_sorted = sorted(original)
print(original)     # [3, 1, 4, 1, 5]  - unchanged
print(new_sorted)   # [1, 1, 3, 4, 5]

# Sorting strings (lexicographic)
words = ["banana", "apple", "Cherry", "date"]
print(sorted(words))            # ['Cherry', 'apple', 'banana', 'date']
print(sorted(words, key=str.lower))  # ['apple', 'banana', 'Cherry', 'date']

# Sort by key function
people = [
    {"name": "Charlie", "age": 35},
    {"name": "Alice",   "age": 25},
    {"name": "Bob",     "age": 30},
]

# Sort by age
by_age = sorted(people, key=lambda p: p["age"])
for p in by_age:
    print(p["name"], p["age"])
# Alice 25
# Bob 30
# Charlie 35

# Sort by multiple keys: primary by age, secondary by name
data = [
    ("Alice", 30), ("Bob", 25), ("Charlie", 30), ("Dave", 25)
]
# Sort by age (index 1) then name (index 0)
from operator import itemgetter
data.sort(key=itemgetter(1, 0))
print(data)
# [('Bob', 25), ('Dave', 25), ('Alice', 30), ('Charlie', 30)]

# min() and max() with key
print(min(people, key=lambda p: p["age"]))  # {'name': 'Alice', 'age': 25}

List Comprehensions

List comprehensions are a concise, readable way to create lists from iterables.

Python
# Basic: [expression for item in iterable]
squares = [x ** 2 for x in range(1, 6)]
print(squares)      # [1, 4, 9, 16, 25]

# 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 strings
words = ["hello", "WORLD", "Python"]
lower = [w.lower() for w in words]
print(lower)        # ['hello', 'world', 'python']

# Filter and transform together
nums = [1, -2, 3, -4, 5, -6]
positives_squared = [x**2 for x in nums if x > 0]
print(positives_squared)    # [1, 9, 25]

# Conditional expression (ternary) inside comprehension
labels = ["positive" if x > 0 else "negative" for x in nums]
print(labels)   # ['positive', 'negative', 'positive', 'negative', 'positive', 'negative']

# Nested comprehension (flatten a 2D list)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [num for row in matrix for num in row]
print(flat)     # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Equivalent regular loop
flat2 = []
for row in matrix:
    for num in row:
        flat2.append(num)

# Transpose a matrix
transposed = [[row[i] for row in matrix] for i in range(3)]
print(transposed)  # [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

# Practical: parse CSV line
csv_line = "Alice,30,Engineer,London"
fields = [f.strip() for f in csv_line.split(",")]
print(fields)   # ['Alice', '30', 'Engineer', 'London']

Nested Lists

Lists can contain other lists, creating multi-dimensional structures like matrices or tables.

Python
# 2D list (matrix)
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]

# Access: matrix[row][col]
print(matrix[0][0])     # 1  - top left
print(matrix[1][1])     # 5  - center
print(matrix[2][-1])    # 9  - bottom right

# Modify
matrix[0][0] = 100
print(matrix[0])        # [100, 2, 3]

# Iterate over rows
for row in matrix:
    print(row)

# Iterate over all cells
for row in matrix:
    for cell in row:
        print(cell, end=" ")
print()

# Create with comprehension
size = 3
grid = [[0] * size for _ in range(size)]
print(grid)     # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

# WARNING: do NOT use this pattern - all rows are the same object
bad_grid = [[0] * size] * size   # wrong!
bad_grid[0][0] = 1
print(bad_grid)  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] - all rows changed!

# Use list() to count/sum columns and rows
col_sums = [sum(matrix[r][c] for r in range(3)) for c in range(3)]
row_sums = [sum(row) for row in matrix]

Copying Lists

Python lists store references. Understanding shallow vs deep copy is critical to avoid unexpected mutations.

Python
import copy

# Assignment - both names point to the same list
a = [1, 2, 3]
b = a           # b is NOT a copy - it's the same object
b.append(4)
print(a)        # [1, 2, 3, 4]  - a was also modified!

# Shallow copy - creates new list, but nested objects are still shared
original = [[1, 2], [3, 4]]
shallow  = original.copy()    # or original[:] or list(original)

shallow.append([5, 6])        # only in shallow, not original
print(original)   # [[1, 2], [3, 4]]  - OK

shallow[0][0] = 99            # modifies nested list - shared reference!
print(original)   # [[99, 2], [3, 4]] - original also changed!

# Deep copy - fully independent, even nested structures
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)

deep[0][0] = 99               # modifies deep copy only
print(original)   # [[1, 2], [3, 4]]  - not affected

# Summary: which to use?
# = assignment    -> same object (aliasing), not a copy
# .copy() / [:]   -> shallow copy (fine for flat lists)
# copy.deepcopy() -> deep copy   (required for nested mutable objects)

# Flat list - shallow copy is safe
nums = [1, 2, 3]
nums_copy = nums.copy()
nums_copy[0] = 99
print(nums)       # [1, 2, 3]  - not affected (ints are immutable)
Shallow copy is enough for flat lists

For lists that only contain immutable items (ints, strings, tuples), a shallow copy is safe. Only use copy.deepcopy() when the list contains mutable objects (other lists, dicts, custom objects) that you intend to modify independently. Deep copy is slower, so prefer shallow copy by default.

Frequently Asked Questions

append(x) adds a single item to the end of the list - if you append a list, the list becomes a nested element. extend(iterable) unpacks the iterable and adds each item individually. For example: [1,2].append([3,4]) gives [1, 2, [3, 4]], while [1,2].extend([3,4]) gives [1, 2, 3, 4]. The += operator on lists behaves like extend().

list.sort() sorts the list in place and returns None - the original list is modified. sorted(iterable) is a built-in function that returns a new sorted list - the original is unchanged. Use sort() when you want to modify the list and don't need the original order. Use sorted() when you want to preserve the original, or when working with non-list iterables like tuples, sets, or generators.

You have three options: list.remove(value) removes the first occurrence of a value (raises ValueError if not found); list.pop(index) removes and returns the item at an index (default is last item); del list[index] deletes an item or slice by position. For checking existence before removing: if x in my_list: my_list.remove(x). To remove all occurrences: use a list comprehension.

A list comprehension is a concise way to create a list: [expression for item in iterable if condition]. Use it when you would otherwise write a for loop that appends to a list. They are more readable, slightly faster, and Pythonic for simple transformations and filtering. Avoid them when the logic is complex (3+ conditions, nested comprehensions deeper than 2 levels) - a regular loop is clearer in those cases.