Skip to content

05-01: Lists — The Complete Guide

A list is Python's most versatile built-in data structure. It is an ordered, mutable sequence that can hold items of any type.


Creating Lists

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

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

# List of strings
fruits = ["apple", "banana", "cherry"]

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

# List of lists (2D)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# From range
evens = list(range(0, 10, 2))   # [0, 2, 4, 6, 8]

# From string
chars = list("Python")    # ['P', 'y', 't', 'h', 'o', 'n']

Indexing

Lists are zero-indexed. Negative indices count from the end.

fruits = ["apple", "banana", "cherry", "date", "elderberry"]
#          0        1         2         3       4
#         -5       -4        -3        -2      -1

print(fruits[0])    # 'apple'
print(fruits[2])    # 'cherry'
print(fruits[-1])   # 'elderberry'  (last)
print(fruits[-2])   # 'date'

# IndexError for out-of-range
print(fruits[10])   # IndexError!

Slicing

Syntax: list[start:stop:step]

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

print(nums[2:6])     # [2, 3, 4, 5]
print(nums[:4])      # [0, 1, 2, 3]
print(nums[5:])      # [5, 6, 7, 8, 9]
print(nums[:])       # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  (full copy)
print(nums[::2])     # [0, 2, 4, 6, 8]   (every 2nd)
print(nums[1::2])    # [1, 3, 5, 7, 9]   (odd indices)
print(nums[::-1])    # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]  (reversed)
print(nums[7:2:-1])  # [7, 6, 5, 4, 3]   (reverse slice)

Modifying Lists (Mutable)

Change a single element

fruits = ["apple", "banana", "cherry"]
fruits[1] = "blueberry"
print(fruits)   # ['apple', 'blueberry', 'cherry']

Change a slice

nums = [1, 2, 3, 4, 5]
nums[1:4] = [20, 30, 40]
print(nums)   # [1, 20, 30, 40, 5]

# Replace with fewer items (list shrinks)
nums[1:4] = [99]
print(nums)   # [1, 99, 5]

# Replace with more items (list grows)
nums[1:2] = [10, 11, 12]
print(nums)   # [1, 10, 11, 12, 5]

# Delete via empty replacement
nums[1:3] = []
print(nums)   # [1, 12, 5]

Adding Elements

append() — Add to end

fruits = ["apple", "banana"]
fruits.append("cherry")
print(fruits)   # ['apple', 'banana', 'cherry']

# append() adds the item as a single element
fruits.append(["date", "elderberry"])
print(fruits)   # ['apple', 'banana', 'cherry', ['date', 'elderberry']]

insert() — Add at position

fruits = ["apple", "cherry"]
fruits.insert(1, "banana")    # insert at index 1
print(fruits)   # ['apple', 'banana', 'cherry']

fruits.insert(0, "avocado")   # insert at beginning
print(fruits)   # ['avocado', 'apple', 'banana', 'cherry']

fruits.insert(100, "zucchini")  # beyond end → appended
print(fruits)   # ['avocado', 'apple', 'banana', 'cherry', 'zucchini']

extend() — Add multiple items from another iterable

fruits = ["apple", "banana"]
fruits.extend(["cherry", "date"])
print(fruits)   # ['apple', 'banana', 'cherry', 'date']

# extend vs append
a = [1, 2]
a.append([3, 4])    # adds list as single element
print(a)            # [1, 2, [3, 4]]

b = [1, 2]
b.extend([3, 4])    # adds elements individually
print(b)            # [1, 2, 3, 4]

+ operator — Concatenate

a = [1, 2, 3]
b = [4, 5, 6]
c = a + b
print(c)    # [1, 2, 3, 4, 5, 6]
print(a)    # [1, 2, 3]  — original unchanged

# += modifies in place
a += [7, 8]
print(a)    # [1, 2, 3, 7, 8]

* operator — Repeat

zeros = [0] * 5
print(zeros)    # [0, 0, 0, 0, 0]

pattern = [1, 2] * 3
print(pattern)  # [1, 2, 1, 2, 1, 2]

Removing Elements

remove() — Remove first occurrence of value

fruits = ["apple", "banana", "cherry", "banana"]
fruits.remove("banana")    # removes first 'banana'
print(fruits)   # ['apple', 'cherry', 'banana']

# ValueError if not found
fruits.remove("mango")    # ValueError!

pop() — Remove and return by index

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

last = fruits.pop()       # default: removes last
print(last)     # 'cherry'
print(fruits)   # ['apple', 'banana']

first = fruits.pop(0)     # remove at index 0
print(first)    # 'apple'
print(fruits)   # ['banana']

del statement — Delete by index or slice

nums = [10, 20, 30, 40, 50]
del nums[2]
print(nums)     # [10, 20, 40, 50]

del nums[1:3]
print(nums)     # [10, 50]

del nums        # delete the variable entirely
# print(nums)   # NameError!

clear() — Remove all elements

fruits = ["apple", "banana", "cherry"]
fruits.clear()
print(fruits)   # []

Searching

index() — Find position of first occurrence

fruits = ["apple", "banana", "cherry", "banana"]
print(fruits.index("banana"))      # 1 (first occurrence)
print(fruits.index("banana", 2))   # 3 (start search from index 2)

# ValueError if not found
print(fruits.index("mango"))       # ValueError!

# Safe way with `in` check first
if "mango" in fruits:
    print(fruits.index("mango"))
else:
    print("not found")

count() — Count occurrences

nums = [1, 2, 3, 2, 1, 2, 4, 1]
print(nums.count(2))   # 3
print(nums.count(1))   # 3
print(nums.count(5))   # 0

in and not in

fruits = ["apple", "banana", "cherry"]
print("banana" in fruits)      # True
print("mango" in fruits)       # False
print("mango" not in fruits)   # True

Sorting

sort() — Sort in place

nums = [3, 1, 4, 1, 5, 9, 2, 6]
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]

# Sort strings
words = ["banana", "Apple", "cherry", "date"]
words.sort()
print(words)    # ['Apple', 'banana', 'cherry', 'date']  (uppercase first)

words.sort(key=str.lower)   # case-insensitive
print(words)    # ['Apple', 'banana', 'cherry', 'date']

# Sort by key function
words.sort(key=len)         # by length
print(words)    # ['Apple', 'date', 'banana', 'cherry']

sorted() — Return new sorted list

nums = [3, 1, 4, 1, 5, 9]
s = sorted(nums)
print(s)        # [1, 1, 3, 4, 5, 9]
print(nums)     # [3, 1, 4, 1, 5, 9]  — unchanged!

# Works on any iterable
s2 = sorted("hello")
print(s2)       # ['e', 'h', 'l', 'l', 'o']

reverse() — Reverse in place

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

Length and Other Info

fruits = ["apple", "banana", "cherry"]
print(len(fruits))    # 3
print(min(fruits))    # 'apple'  (lexicographic)
print(max(fruits))    # 'cherry'

nums = [3, 1, 4, 1, 5, 9]
print(len(nums))      # 6
print(min(nums))      # 1
print(max(nums))      # 9
print(sum(nums))      # 23

Copying Lists

Careful — assignment creates a reference, not a copy!

a = [1, 2, 3]
b = a           # b is another name for the SAME list
b.append(4)
print(a)        # [1, 2, 3, 4]  — a is affected!

Shallow copy

# Method 1: slice
a = [1, 2, 3]
b = a[:]        # shallow copy

# Method 2: copy()
b = a.copy()

# Method 3: list()
b = list(a)

b.append(4)
print(a)        # [1, 2, 3]  — unchanged
print(b)        # [1, 2, 3, 4]

Deep copy (for nested lists)

import copy
matrix = [[1, 2], [3, 4]]
deep = copy.deepcopy(matrix)
deep[0][0] = 99
print(matrix)   # [[1, 2], [3, 4]]  — unchanged
print(deep)     # [[99, 2], [3, 4]]

Iterating

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

# Basic iteration
for fruit in fruits:
    print(fruit)

# With index — enumerate
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")

# Iterate backwards
for fruit in reversed(fruits):
    print(fruit)

# Iterate with zip
prices = [1.5, 0.75, 2.0]
for fruit, price in zip(fruits, prices):
    print(f"{fruit}: ${price:.2f}")

List as Stack (LIFO)

stack = []
stack.append("a")    # push
stack.append("b")
stack.append("c")

top = stack.pop()    # pop
print(top)           # 'c'
print(stack)         # ['a', 'b']

List as Queue (FIFO)

For queue behavior, use collections.deque for efficiency:

from collections import deque

queue = deque()
queue.append("first")    # enqueue
queue.append("second")
queue.append("third")

front = queue.popleft()  # dequeue
print(front)             # 'first'
print(queue)             # deque(['second', 'third'])

2D Lists (Matrix)

# Create 3×3 matrix
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# Access element
print(matrix[1][2])    # 6  (row 1, col 2)

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

# Iterate all elements
for row in matrix:
    for val in row:
        print(val, end=" ")
    print()

# Get a column
col1 = [row[1] for row in matrix]
print(col1)   # [2, 5, 8]

Complete Method Reference

Method Description Returns
append(x) Add x to end None
insert(i, x) Insert x before index i None
extend(iterable) Add all items from iterable None
remove(x) Remove first x None
pop([i]) Remove & return item at i (default last) item
clear() Remove all items None
index(x[,start[,end]]) Find first index of x int
count(x) Count occurrences of x int
sort(key=None, reverse=False) Sort in place None
reverse() Reverse in place None
copy() Return shallow copy list

Practice Problems

# 1. Rotate list left by k positions
def rotate_left(lst, k):
    k = k % len(lst)
    return lst[k:] + lst[:k]

print(rotate_left([1, 2, 3, 4, 5], 2))   # [3, 4, 5, 1, 2]

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

print(remove_duplicates([1, 3, 2, 1, 4, 3, 5]))   # [1, 3, 2, 4, 5]

# 3. Flatten nested list
def flatten(nested):
    result = []
    for item in nested:
        if isinstance(item, list):
            result.extend(flatten(item))
        else:
            result.append(item)
    return result

print(flatten([1, [2, [3, 4], 5], 6]))   # [1, 2, 3, 4, 5, 6]

# 4. Second largest
def second_largest(nums):
    unique = sorted(set(nums), reverse=True)
    return unique[1] if len(unique) >= 2 else None

print(second_largest([3, 1, 4, 1, 5, 9, 2, 6]))   # 6

# 5. Chunk list into groups of n
def chunks(lst, n):
    return [lst[i:i+n] for i in range(0, len(lst), n)]

print(chunks([1,2,3,4,5,6,7,8,9], 3))   # [[1,2,3],[4,5,6],[7,8,9]]

Exercises: 05-01: Exercises — Lists


⬅️ Previous: 04-01: Functions — Basics ➡️ Next: 05-02: Tuples