Python Lists — The Complete Guide
💡 Lists are the most widely used data structure in Python. This comprehensive guide covers everything from basic creation to advanced techniques, giving you complete mastery over Python's most versatile collection type.
Learning Objectives
By the end of this tutorial, you will be able to:
- Create lists using various methods and understand when to use each
- Use indexing and slicing to access and manipulate list elements
- Apply all built-in list methods confidently
- Use all related built-in functions:
len(), min(), max(), sum(), sorted(), reversed(), enumerate(), zip(), map(), filter(), any(), all()
- Write basic and intermediate list comprehensions
- Understand nested lists and 2D data structures
- Recognize performance implications of different operations
- Avoid common pitfalls that trip up Python developers
What Are Lists?
A list is an ordered, mutable collection in Python. Think of it as a container that holds multiple items in a specific sequence.
mixed = [42, "hello", 3.14, True, None, [1, 2, 3]]
print(mixed)
# Output: [42, 'hello', 3.14, True, None, [1, 2, 3]]
Key Characteristics
| Characteristic | Description | Example |
|---|
| Ordered | Elements maintain their insertion order | [1, 2, 3] is different from [3, 2, 1] |
| Mutable | Elements can be changed after creation | lst[0] = 99 works fine |
| Dynamic | Can grow or shrink at runtime | lst.append(4) adds an element |
| Heterogeneous | Can mix different types | [1, "two", 3.0] is valid |
| Duplicates allowed | Same value can appear multiple times | [1, 1, 2, 3] is valid |
| Zero-indexed | First element is at index 0 | lst[0] gets the first item |
Creating Lists
1. Literal Syntax (Most Common)
empty = []
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
mixed = [1, "two", 3.0, True]
2. The list() Constructor
chars = list("Python") # ['P', 'y', 't', 'h', 'o', 'n']
numbers = list(range(1, 6)) # [1, 2, 3, 4, 5]
from_tuple = list((10, 20, 30)) # [10, 20, 30]
3. List Multiplication
zeros = [0] * 5 # [0, 0, 0, 0, 0]
pattern = [0, 1] * 4 # [0, 1, 0, 1, 0, 1, 0, 1]
# WARNING: Be careful with mutable objects
nested = [[0]] * 3
nested[0][0] = 99
print(nested) # [[99], [99], [99]] — All sublists are the same object!
4. List Comprehension
squares = [x**2 for x in range(5)]
print(squares) # [0, 1, 4, 9, 16]
evens = [x for x in range(10) if x % 2 == 0]
print(evens) # [0, 2, 4, 6, 8]
Indexing and Slicing
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
# 0 1 2 3 4
print(fruits[0]) # apple
print(fruits[2]) # cherry
print(fruits[-1]) # elderberry
# Slicing
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:5]) # [2, 3, 4]
print(numbers[:4]) # [0, 1, 2, 3]
print(numbers[6:]) # [6, 7, 8, 9]
print(numbers[::2]) # [0, 2, 4, 6, 8]
print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Slice Assignment (Mutating Slices)
letters = ['a', 'b', 'c', 'd', 'e']
letters[1:3] = ['B', 'C', 'D'] # Replace 2 elements with 3
print(letters) # ['a', 'B', 'C', 'D', 'd', 'e']
letters[1:1] = ['X', 'Y'] # Insert before index 1
print(letters) # ['a', 'X', 'Y', 'B', 'C', 'D', 'd', 'e']
letters[2:5] = [] # Remove elements at indices 2, 3, 4
print(letters) # ['a', 'X', 'B', 'd', 'e']
All List Methods
Adding Elements
| Method | Description | Time Complexity |
|---|
append(x) | Add x to the end | O(1) amortized |
extend(iterable) | Add all items from iterable | O(k) |
insert(i, x) | Insert x at position i | O(n) |
# append() — adds a single element to the end
fruits = ["apple", "banana"]
fruits.append("cherry")
print(fruits) # ['apple', 'banana', 'cherry']
# append a list as a single element
fruits.append(["date", "elderberry"])
print(fruits) # ['apple', 'banana', 'cherry', ['date', 'elderberry']]
# extend() — adds multiple elements from an iterable
fruits = ["apple", "banana"]
fruits.extend(["cherry", "date"])
print(fruits) # ['apple', 'banana', 'cherry', 'date']
# extend vs append
a = [1, 2]
a.extend([3, 4]) # Adds 3 and 4 as separate elements
print(a) # [1, 2, 3, 4]
b = [1, 2]
b.append([3, 4]) # Adds [3, 4] as a single element
print(b) # [1, 2, [3, 4]]
# insert() — adds element at a specific position
names = ["Alice", "Bob", "David"]
names.insert(2, "Charlie") # Insert "Charlie" at index 2
print(names) # ['Alice', 'Bob', 'Charlie', 'David']
names.insert(0, "Zara") # Insert at the beginning
print(names) # ['Zara', 'Alice', 'Bob', 'Charlie', 'David']
Removing Elements
| Method | Description | Time Complexity |
|---|
remove(x) | Remove first occurrence of x | O(n) |
pop(i) | Remove and return element at index i | O(n) for i≠-1, O(1) for i=-1 |
del lst[i] | Remove element at index i | O(n) |
clear() | Remove all elements | O(n) |
# remove() — removes first occurrence of a value
colors = ["red", "blue", "green", "blue"]
colors.remove("blue")
print(colors) # ['red', 'green', 'blue']
# pop() — removes and returns element at index
stack = [10, 20, 30, 40, 50]
last = stack.pop()
print(last) # 50
print(stack) # [10, 20, 30, 40]
second = stack.pop(1)
print(second) # 20
# del — keyword for removing elements
data = [10, 20, 30, 40, 50]
del data[2] # Remove element at index 2
del data[1:3] # Remove a slice
del data[:] # Clear the list
# clear() — remove all elements
items = [1, 2, 3, 4, 5]
items.clear()
print(items) # []
Searching
| Method | Description | Time Complexity |
|---|
index(x) | Return index of first occurrence of x | O(n) |
count(x) | Count occurrences of x | O(n) |
x in lst | Check if x is in the list | O(n) |
# index() — find position of an element
primes = [2, 3, 5, 7, 11, 13, 17]
print(primes.index(7)) # 3
print(primes.index(13)) # 5
# count() — how many times does a value appear?
data = [1, 2, 2, 3, 2, 4, 2]
print(data.count(2)) # 4
print(data.count(5)) # 0
# The 'in' operator
fruits = ["apple", "banana", "cherry"]
print("banana" in fruits) # True
print("grape" in fruits) # False
Sorting
| Method/Function | Description | Returns |
|---|
lst.sort() | Sort list in-place | None |
sorted(lst) | Return new sorted list | New list |
lst.sort(key=fn) | Sort with custom key | None |
lst.sort(reverse=True) | Sort descending | None |
reversed(lst) | Return reverse iterator | Iterator |
lst.reverse() | Reverse in-place | None |
# sort() — in-place sorting
scores = [85, 92, 78, 95, 88]
scores.sort()
print(scores) # [78, 85, 88, 92, 95]
scores.sort(reverse=True)
print(scores) # [95, 92, 88, 85, 78]
# sorted() — returns a new sorted list
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
new_sorted = sorted(numbers)
print(numbers) # [3, 1, 4, 1, 5, 9, 2, 6] — unchanged
print(new_sorted) # [1, 1, 2, 3, 4, 5, 6, 9]
# Sorting with a key function
words = ["banana", "apple", "cherry", "date"]
words.sort(key=len) # Sort by string length
print(words) # ['date', 'apple', 'banana', 'cherry']
# Case-insensitive sorting
names = ["charlie", "Alice", "bob"]
names.sort(key=str.lower)
print(names) # ['Alice', 'bob', 'charlie']
# Sort by specific attribute
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
students.sort(key=lambda student: student[1])
print(students) # [('Charlie', 78), ('Alice', 85), ('Bob', 92)]
# reversed() — returns an iterator
nums = [1, 2, 3]
rev = reversed(nums)
print(list(rev)) # [3, 2, 1]
print(nums) # [1, 2, 3] — unchanged
# reverse() — in-place reversal
nums = [1, 2, 3]
nums.reverse()
print(nums) # [3, 2, 1]
Other Methods
# copy() — create a shallow copy
original = [1, 2, 3, [4, 5]]
copied = original.copy()
copied[0] = 99
print(original) # [1, 2, 3, [4, 5]] — unchanged
# But shallow copy has caveats with nested objects
copied[3][0] = 99
print(original) # [1, 2, 3, [99, 5]] — nested list is shared!
Related Built-In Functions
len() — Number of Elements
numbers = [4, 2, 7, 1, 9, 3]
print(len(numbers)) # 6
words = ["apple", "banana", "cherry"]
print(len(words)) # 3
print(len([])) # 0
min() and max() — Minimum and Maximum
numbers = [4, 2, 7, 1, 9, 3]
print(min(numbers)) # 1
print(max(numbers)) # 9
# With strings (lexicographic)
words = ["apple", "banana", "cherry"]
print(min(words)) # 'apple'
print(max(words)) # 'cherry'
# With key function
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
print(min(students, key=lambda s: s[1])) # ('Charlie', 78)
print(max(students, key=lambda s: s[1])) # ('Bob', 92)
sum() — Sum of Elements
numbers = [1, 2, 3, 4, 5]
print(sum(numbers)) # 15
# With start value
print(sum(numbers, 10)) # 25
# Sum of booleans (True=1, False=0)
booleans = [True, False, True, True, False]
print(sum(booleans)) # 3
sorted() — Return New Sorted List
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(sorted(numbers)) # [1, 1, 2, 3, 4, 5, 6, 9]
# With key
words = ["banana", "apple", "cherry"]
print(sorted(words, key=len)) # ['apple', 'banana', 'cherry']
# With reverse
print(sorted(numbers, reverse=True)) # [9, 6, 5, 4, 3, 2, 1, 1]
reversed() — Return Reverse Iterator
numbers = [1, 2, 3, 4, 5]
rev = reversed(numbers)
print(list(rev)) # [5, 4, 3, 2, 1]
print(numbers) # [1, 2, 3, 4, 5] — unchanged
# Useful in for loops
for num in reversed([1, 2, 3, 4, 5]):
print(num, end=" ")
# 5 4 3 2 1
enumerate() — Index and Value Pairs
fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
# 0: apple
# 1: banana
# 2: cherry
# With custom start
for index, fruit in enumerate(fruits, start=1):
print(f"{index}. {fruit}")
# 1. apple
# 2. banana
# 3. cherry
zip() — Combine Multiple Iterables
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
grades = ["A", "A+", "B+"]
# Zip two lists
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Zip three lists
for name, score, grade in zip(names, scores, grades):
print(f"{name}: {score} ({grade})")
# Convert to list of tuples
pairs = list(zip(names, scores))
print(pairs) # [('Alice', 85), ('Bob', 92), ('Charlie', 78)]
map() — Apply Function to Each Element
numbers = [1, 2, 3, 4, 5]
# Square each number
squared = list(map(lambda x: x**2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# With multiple iterables
a = [1, 2, 3]
b = [10, 20, 30]
sums = list(map(lambda x, y: x + y, a, b))
print(sums) # [11, 22, 33]
# Convert strings to integers
str_nums = ["1", "2", "3", "4", "5"]
int_nums = list(map(int, str_nums))
print(int_nums) # [1, 2, 3, 4, 5]
filter() — Keep Elements Where Function Returns True
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Keep even numbers
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6, 8, 10]
# Keep strings longer than 3 characters
words = ["hi", "hello", "hey", "howdy", "yo"]
long_words = list(filter(lambda w: len(w) > 3, words))
print(long_words) # ['hello', 'howdy']
# Filter out None values
data = [1, None, 2, None, 3]
clean = list(filter(None, data))
print(clean) # [1, 2, 3]
any() and all() — Boolean Checks
numbers = [0, 1, 2, 3, 4]
print(any(numbers)) # True — at least one truthy value
print(all(numbers)) # False — not all truthy (0 is falsy)
numbers = [1, 2, 3, 4, 5]
print(all(numbers)) # True — all truthy
# With conditions
words = ["hello", "world", "python"]
print(all(len(w) > 3 for w in words)) # True
print(any(len(w) > 10 for w in words)) # False
# Check if any element meets condition
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
print(any(score >= 90 for _, score in students)) # True
print(all(score >= 90 for _, score in students)) # False
List Comprehensions
Basic Syntax
squares = [x**2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Filtering with Conditions
evens = [x for x in range(20) if x % 2 == 0]
print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
long_words = [word.upper() for word in ["hi", "hello", "hey", "howdy"] if len(word) > 2]
print(long_words) # ['HELLO', 'HOWDY']
Nested Comprehensions
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]
identity = [[1 if i == j else 0 for j in range(3)] for i in range(3)]
print(identity) # [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
Conditional Expressions (Ternary)
labels = ["even" if x % 2 == 0 else "odd" for x in range(5)]
print(labels) # ['even', 'odd', 'even', 'odd', 'even']
Nested Lists
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(matrix[0][0]) # 1
print(matrix[1][2]) # 6
# Matrix addition
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
C = [[A[i][j] + B[i][j] for j in range(2)] for i in range(2)]
print(C) # [[6, 8], [10, 12]]
# Transpose
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transposed = [[row[i] for row in matrix] for i in range(3)]
print(transposed) # [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Deep Copy vs Shallow Copy
import copy
original = [[1, 2, 3], [4, 5, 6]]
# Shallow copy — nested objects are shared
shallow = original.copy()
shallow[0][0] = 99
print(original[0][0]) # 99 — original is affected!
# Deep copy — everything is copied
original = [[1, 2, 3], [4, 5, 6]]
deep = copy.deepcopy(original)
deep[0][0] = 99
print(original[0][0]) # 1 — original is safe
Lists vs Other Sequences
Lists vs Tuples
| Feature | List [] | Tuple () |
|---|
| Mutable | Yes | No |
| Syntax | [1, 2, 3] | (1, 2, 3) |
| Performance | Slightly slower | Slightly faster |
| Use case | Dynamic collections | Fixed records |
| Dictionary key | No | Yes (if hashable) |
Lists vs Generators
| Feature | List | Generator |
|---|
| Memory | Stores all elements | Produces on demand |
| Iteration | Multiple times | Single pass |
| Indexing | Supports lst[i] | No random access |
import sys
# List — all elements stored in memory
squares_list = [x**2 for x in range(1_000_000)]
# Generator — produces values on demand
squares_gen = (x**2 for x in range(1_000_000))
print(sys.getsizeof(squares_list)) # ~8,448,728 bytes
print(sys.getsizeof(squares_gen)) # ~208 bytes
Common Patterns
Unpacking
first, second, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [3, 4, 5]
first, _, third, *_ = [1, 2, 3, 4, 5]
print(third) # 3
# Unpacking in function arguments
def greet(name, age):
return f"{name} is {age} years old"
person = ["Alice", 30]
print(greet(*person)) # "Alice is 30 years old"
Removing Duplicates (Preserving Order)
def remove_duplicates(lst):
seen = set()
result = []
for item in lst:
if item not in seen:
seen.add(item)
result.append(item)
return result
data = [3, 1, 2, 3, 1, 4, 2, 5]
print(remove_duplicates(data)) # [3, 1, 2, 4, 5]
# Simpler approach (Python 3.7+)
unique = list(dict.fromkeys(data))
print(unique) # [3, 1, 2, 4, 5]
Flattening Nested Lists
nested = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
flat = [item for sublist in nested for item in sublist]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
from itertools import chain
flat = list(chain.from_iterable(nested))
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
Performance Considerations
| Operation | Time Complexity | Notes |
|---|
append(x) | O(1) amortized | Fast |
pop() (from end) | O(1) | Fast |
pop(0) (from start) | O(n) | Slow — all elements shift |
insert(0, x) | O(n) | Slow — all elements shift |
x in lst | O(n) | Linear search |
lst[i] | O(1) | Random access is fast |
len(lst) | O(1) | Length is cached |
sort() | O(n log n) | Timsort algorithm |
copy() | O(n) | Must copy all elements |
When to Use collections.deque
from collections import deque
# deque is optimized for append/popleft operations
dq = deque([1, 2, 3])
dq.appendleft(0) # O(1) — fast regardless of size
# List — O(n) for insert at start
lst = [1, 2, 3]
lst.insert(0, 0) # Slow for large lists
Common Mistakes
1. Mutable Default Arguments
# WRONG
def add_item(item, lst=[]):
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] — unexpected!
# CORRECT
def add_item(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [2]
2. Modifying While Iterating
# WRONG
numbers = [1, 2, 3, 4, 5, 6]
for num in numbers:
if num % 2 == 0:
numbers.remove(num)
# CORRECT
numbers = [1, 2, 3, 4, 5, 6]
numbers = [num for num in numbers if num % 2 != 0]
3. == vs is for Lists
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True — same values
print(a is b) # False — different objects
print(a is c) # True — same object
4. Forgetting sort() Returns None
numbers = [3, 1, 4, 1, 5]
result = numbers.sort()
print(result) # None!
# Use sorted() to get a new list
sorted_numbers = sorted(numbers)
Practice Exercises
Exercise 1: Remove Duplicates
def remove_duplicates(lst):
seen = set()
result = []
for item in lst:
if item not in seen:
seen.add(item)
result.append(item)
return result
print(remove_duplicates([1, 2, 2, 3, 4, 4, 5])) # [1, 2, 3, 4, 5]
print(remove_duplicates(["a", "b", "a", "c", "b"])) # ['a', 'b', 'c']
Exercise 2: Rotate a List
def rotate_right(lst, k):
if not lst:
return lst
k = k % len(lst)
return lst[-k:] + lst[:-k]
print(rotate_right([1, 2, 3, 4, 5], 2)) # [4, 5, 1, 2, 3]
print(rotate_right([1, 2, 3, 4, 5], 7)) # [4, 5, 1, 2, 3]
Exercise 3: Find Common Elements
def common_elements(lst1, lst2):
return list(set(lst1) & set(lst2))
print(common_elements([1, 2, 3, 4, 5], [3, 4, 5, 6, 7])) # [3, 4, 5]
print(common_elements(["a", "b", "c"], ["b", "c", "d"])) # ['b', 'c']
Key Takeaways
- Lists are ordered, mutable, and can hold any data type — Python's most versatile collection
- Use literal syntax
[] — faster and more Pythonic than list()
append() vs extend() — append adds one element, extend adds multiple
sort() modifies in-place, sorted() returns a new list — choose based on whether you need to preserve the original
- List comprehensions are concise and fast — use them for simple transformations and filters
- Watch out for shallow copies with nested lists — use
copy.deepcopy() when needed
- Avoid mutable default arguments — always use
None as the default
- Consider performance —
insert(0, x) is O(n), use deque for frequent start insertions
- Use
in for membership testing — but remember it's O(n) for lists
- Built-in functions like
len(), min(), max(), sum(), sorted(), enumerate(), zip(), map(), filter(), any(), all() make list operations elegant and efficient