Lists are the workhorse of Python data structures. If you write Python for any length of time, you will use lists more than almost anything else. They are ordered, mutable, and flexible enough to hold any mix of data types. Whether you are storing user records, processing CSV rows, building queues, or transforming datasets, lists are your go-to tool.
In this tutorial we will cover everything you need to work with lists confidently — from creation and manipulation all the way through list comprehensions, copying gotchas, and real-world patterns you will use on the job every day.
There are several ways to create a list in Python. Pick the one that fits your situation.
The most common approach — just use square brackets.
# empty list empty = [] # list of integers numbers = [1, 2, 3, 4, 5] # mixed types — perfectly valid mixed = ["Alice", 30, True, 3.14, None] # nested list matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print(numbers) # [1, 2, 3, 4, 5] print(mixed) # ['Alice', 30, True, 3.14, None]
Converts any iterable into a list.
# from a string
chars = list("hello")
print(chars) # ['h', 'e', 'l', 'l', 'o']
# from a tuple
from_tuple = list((10, 20, 30))
print(from_tuple) # [10, 20, 30]
# from a set (order not guaranteed)
from_set = list({3, 1, 2})
print(from_set) # [1, 2, 3] — order may vary
# from a dictionary (keys only)
from_dict = list({"name": "Alice", "age": 30})
print(from_dict) # ['name', 'age']
Generate sequences of numbers quickly.
# 0 through 9 nums = list(range(10)) print(nums) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 5 through 14 nums = list(range(5, 15)) print(nums) # [5, 6, 7, 8, 9, 10, 11, 12, 13, 14] # even numbers from 0 to 20 evens = list(range(0, 21, 2)) print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20] # countdown countdown = list(range(10, 0, -1)) print(countdown) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
Lists are zero-indexed. You can access elements from the front or the back, and you can slice out sub-lists with ease.
fruits = ["apple", "banana", "cherry", "date", "elderberry"] # first element print(fruits[0]) # apple # third element print(fruits[2]) # cherry # last element print(fruits[-1]) # elderberry # second to last print(fruits[-2]) # date
Slicing uses the syntax list[start:stop:step]. The start index is inclusive, the stop index is exclusive.
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # elements from index 2 to 5 (exclusive) print(numbers[2:5]) # [2, 3, 4] # first three elements print(numbers[:3]) # [0, 1, 2] # from index 7 to the end print(numbers[7:]) # [7, 8, 9] # every other element print(numbers[::2]) # [0, 2, 4, 6, 8] # reverse the list print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] # last three elements print(numbers[-3:]) # [7, 8, 9] # copy the entire list via slice copy = numbers[:] print(copy) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Lists are mutable — you can change them in place after creation. This is one of the key differences between lists and tuples.
colors = ["red", "green", "blue"] # replace an element colors[1] = "yellow" print(colors) # ['red', 'yellow', 'blue'] # replace a slice colors[1:3] = ["orange", "purple", "pink"] print(colors) # ['red', 'orange', 'purple', 'pink']
Adds a single element to the end of the list. This is O(1) amortized — very fast.
stack = []
stack.append("first")
stack.append("second")
stack.append("third")
print(stack) # ['first', 'second', 'third']
Inserts an element at a specific index. Everything after that index shifts right.
letters = ["a", "c", "d"] letters.insert(1, "b") # insert 'b' at index 1 print(letters) # ['a', 'b', 'c', 'd'] # insert at the beginning letters.insert(0, "z") print(letters) # ['z', 'a', 'b', 'c', 'd']
Appends every element from an iterable to the list. Unlike append(), it unpacks the iterable.
first = [1, 2, 3] second = [4, 5, 6] first.extend(second) print(first) # [1, 2, 3, 4, 5, 6] # extend with any iterable first.extend(range(7, 10)) print(first) # [1, 2, 3, 4, 5, 6, 7, 8, 9] # compare: append vs extend a = [1, 2] a.append([3, 4]) # adds the list as a single element print(a) # [1, 2, [3, 4]] b = [1, 2] b.extend([3, 4]) # adds each element print(b) # [1, 2, 3, 4]
Python gives you several ways to remove elements, each suited to a different scenario.
Removes the first occurrence of a value. Raises ValueError if not found.
fruits = ["apple", "banana", "cherry", "banana"]
fruits.remove("banana") # removes the FIRST 'banana'
print(fruits) # ['apple', 'cherry', 'banana']
# safe removal
try:
fruits.remove("mango")
except ValueError:
print("mango not in list")
Removes and returns an element by index. Defaults to the last element if no index is given.
numbers = [10, 20, 30, 40, 50] # pop the last element last = numbers.pop() print(last) # 50 print(numbers) # [10, 20, 30, 40] # pop at a specific index second = numbers.pop(1) print(second) # 20 print(numbers) # [10, 30, 40]
Removes by index or slice. Does not return the removed value.
letters = ["a", "b", "c", "d", "e"] # delete a single element del letters[2] print(letters) # ['a', 'b', 'd', 'e'] # delete a slice del letters[1:3] print(letters) # ['a', 'e'] # delete the entire list del letters # print(letters) # NameError: name 'letters' is not defined
Empties the list but keeps the list object itself.
items = [1, 2, 3, 4, 5] items.clear() print(items) # [] print(type(items)) # <class 'list'>
Python lists support a handful of operators that make common tasks concise.
# Concatenation with +
list_a = [1, 2, 3]
list_b = [4, 5, 6]
combined = list_a + list_b
print(combined) # [1, 2, 3, 4, 5, 6]
# Repetition with *
zeros = [0] * 5
print(zeros) # [0, 0, 0, 0, 0]
pattern = [1, 2] * 3
print(pattern) # [1, 2, 1, 2, 1, 2]
# Membership with in
fruits = ["apple", "banana", "cherry"]
print("banana" in fruits) # True
print("mango" in fruits) # False
print("mango" not in fruits) # True
# Length with len()
print(len(fruits)) # 3
# Count occurrences
numbers = [1, 2, 2, 3, 3, 3]
print(numbers.count(3)) # 3
# Find index of first occurrence
print(numbers.index(2)) # 1
Sorting is one of the most common operations. Python gives you two choices: sort in place or return a new sorted list.
Modifies the original list and returns None.
numbers = [3, 1, 4, 1, 5, 9, 2, 6] # ascending (default) numbers.sort() print(numbers) # [1, 1, 2, 3, 4, 5, 6, 9] # descending numbers.sort(reverse=True) print(numbers) # [9, 6, 5, 4, 3, 2, 1, 1]
Leaves the original untouched.
original = [3, 1, 4, 1, 5] sorted_list = sorted(original) print(original) # [3, 1, 4, 1, 5] — unchanged print(sorted_list) # [1, 1, 3, 4, 5]
The key parameter accepts a function that extracts a comparison key from each element.
# sort strings by length
words = ["python", "is", "a", "powerful", "language"]
words.sort(key=len)
print(words) # ['a', 'is', 'python', 'powerful', 'language']
# sort by absolute value
numbers = [-5, 3, -1, 8, -2]
sorted_nums = sorted(numbers, key=abs)
print(sorted_nums) # [-1, -2, 3, -5, 8]
# sort dictionaries by a specific key
students = [
{"name": "Alice", "grade": 88},
{"name": "Bob", "grade": 95},
{"name": "Charlie", "grade": 72},
]
# sort by grade descending
students.sort(key=lambda s: s["grade"], reverse=True)
for s in students:
print(f"{s['name']}: {s['grade']}")
# Bob: 95
# Alice: 88
# Charlie: 72
# multi-key sort: by grade descending, then name ascending
students_v2 = [
{"name": "Alice", "grade": 88},
{"name": "Bob", "grade": 88},
{"name": "Charlie", "grade": 95},
]
students_v2.sort(key=lambda s: (-s["grade"], s["name"]))
for s in students_v2:
print(f"{s['name']}: {s['grade']}")
# Charlie: 95
# Alice: 88
# Bob: 88
List comprehensions are one of the most Pythonic features in the language. They let you create new lists by transforming and filtering existing iterables — all in a single, readable expression.
# syntax: [expression for item in iterable] # squares of 0 through 9 squares = [x**2 for x in range(10)] print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # uppercase words words = ["hello", "world", "python"] upper = [w.upper() for w in words] print(upper) # ['HELLO', 'WORLD', 'PYTHON'] # string formatting names = ["alice", "bob", "charlie"] formatted = [name.title() for name in names] print(formatted) # ['Alice', 'Bob', 'Charlie']
# syntax: [expression for item in iterable if condition] # even numbers only evens = [x for x in range(20) if x % 2 == 0] print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] # words longer than 4 characters words = ["hi", "hello", "hey", "howdy", "greetings"] long_words = [w for w in words if len(w) > 4] print(long_words) # ['hello', 'howdy', 'greetings'] # filter and transform numbers = [1, -2, 3, -4, 5, -6] positive_squares = [x**2 for x in numbers if x > 0] print(positive_squares) # [1, 9, 25]
When you need an else clause, the conditional goes before the for.
# syntax: [expr_if_true if condition else expr_if_false for item in iterable] numbers = [1, -2, 3, -4, 5] abs_values = [x if x >= 0 else -x for x in numbers] print(abs_values) # [1, 2, 3, 4, 5] labels = ["even" if x % 2 == 0 else "odd" for x in range(6)] print(labels) # ['even', 'odd', 'even', 'odd', 'even', 'odd']
# 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]
# create a multiplication table
table = [[i * j for j in range(1, 6)] for i in range(1, 6)]
for row in table:
print(row)
# [1, 2, 3, 4, 5]
# [2, 4, 6, 8, 10]
# [3, 6, 9, 12, 15]
# [4, 8, 12, 16, 20]
# [5, 10, 15, 20, 25]
# all pairs from two lists
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
combinations = [(c, s) for c in colors for s in sizes]
print(combinations)
# [('red', 'S'), ('red', 'M'), ('red', 'L'), ('blue', 'S'), ('blue', 'M'), ('blue', 'L')]
numbers = [1, 2, 3, 4, 5] # map approach squares_map = list(map(lambda x: x**2, numbers)) # comprehension approach — generally preferred squares_comp = [x**2 for x in numbers] print(squares_map) # [1, 4, 9, 16, 25] print(squares_comp) # [1, 4, 9, 16, 25] # filter approach evens_filter = list(filter(lambda x: x % 2 == 0, numbers)) # comprehension approach evens_comp = [x for x in numbers if x % 2 == 0] print(evens_filter) # [2, 4] print(evens_comp) # [2, 4] # comprehensions are almost always more readable # use map/filter when passing an existing function result = list(map(str, numbers)) # this is clean enough print(result) # ['1', '2', '3', '4', '5']
Lists can contain other lists, which gives you an easy way to represent tables, grids, and matrices.
# 2D list (matrix)
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# access element at row 1, column 2
print(matrix[1][2]) # 6
# access entire row
print(matrix[0]) # [1, 2, 3]
# access a column (no built-in syntax — use comprehension)
col_1 = [row[1] for row in matrix]
print(col_1) # [2, 5, 8]
# iterate over all elements
for i, row in enumerate(matrix):
for j, val in enumerate(row):
print(f"matrix[{i}][{j}] = {val}")
# create an m x n grid of zeros
rows, cols = 3, 4
grid = [[0] * cols for _ in range(rows)]
for row in grid:
print(row)
# [0, 0, 0, 0]
# [0, 0, 0, 0]
# [0, 0, 0, 0]
# WARNING: do NOT create a grid this way
bad_grid = [[0] * cols] * rows # all rows reference the SAME list!
bad_grid[0][0] = 99
print(bad_grid) # [[99, 0, 0, 0], [99, 0, 0, 0], [99, 0, 0, 0]] — oops!
nested = [[1, 2], [3, 4], [5, 6]]
# comprehension
flat = [item for sublist in nested for item in sublist]
print(flat) # [1, 2, 3, 4, 5, 6]
# itertools approach for deeply nested structures
import itertools
flat_iter = list(itertools.chain.from_iterable(nested))
print(flat_iter) # [1, 2, 3, 4, 5, 6]
# recursive flattening for arbitrary depth
def flatten(lst):
result = []
for item in lst:
if isinstance(item, list):
result.extend(flatten(item))
else:
result.append(item)
return result
deeply_nested = [1, [2, [3, [4, [5]]]]]
print(flatten(deeply_nested)) # [1, 2, 3, 4, 5]
This is where many Python developers get burned. Understanding the difference between assignment, shallow copy, and deep copy is essential.
original = [1, 2, 3] alias = original # NOT a copy — both names point to the same list alias.append(4) print(original) # [1, 2, 3, 4] — original is modified! print(alias is original) # True
Creates a new list, but inner objects are still shared references.
import copy original = [1, 2, 3] # three ways to shallow copy copy1 = original.copy() copy2 = original[:] copy3 = list(original) copy4 = copy.copy(original) copy1.append(4) print(original) # [1, 2, 3] — original is safe print(copy1) # [1, 2, 3, 4] # but with nested lists, shallow copy is not enough nested = [[1, 2], [3, 4]] shallow = nested.copy() shallow[0][0] = 99 # modifies the inner list print(nested) # [[99, 2], [3, 4]] — original is affected! print(shallow) # [[99, 2], [3, 4]]
Recursively copies everything. Use this when your list contains mutable objects.
import copy nested = [[1, 2], [3, 4]] deep = copy.deepcopy(nested) deep[0][0] = 99 print(nested) # [[1, 2], [3, 4]] — original is safe print(deep) # [[99, 2], [3, 4]]
Python ships with many built-in functions that work beautifully with lists.
numbers = [10, 20, 30, 40, 50] print(len(numbers)) # 5 print(sum(numbers)) # 150 print(min(numbers)) # 10 print(max(numbers)) # 50 # any() — True if at least one element is truthy print(any([0, 0, 1])) # True print(any([0, 0, 0])) # False # all() — True if every element is truthy print(all([1, 2, 3])) # True print(all([1, 0, 3])) # False # practical: check if all scores pass scores = [75, 82, 91, 68, 88] all_passing = all(s >= 60 for s in scores) print(all_passing) # True
Stop writing for i in range(len(list)). Use enumerate instead.
fruits = ["apple", "banana", "cherry"]
# bad — do not do this
for i in range(len(fruits)):
print(i, fruits[i])
# good — use enumerate
for i, fruit in enumerate(fruits):
print(i, fruit)
# start counting from 1
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
# 1. apple
# 2. banana
# 3. cherry
names = ["Alice", "Bob", "Charlie"]
scores = [88, 95, 72]
grades = ["B+", "A", "C"]
for name, score, grade in zip(names, scores, grades):
print(f"{name}: {score} ({grade})")
# Alice: 88 (B+)
# Bob: 95 (A)
# Charlie: 72 (C)
# create a dictionary from two lists
name_score = dict(zip(names, scores))
print(name_score) # {'Alice': 88, 'Bob': 95, 'Charlie': 72}
# unzip with zip(*...)
pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
nums, letters = zip(*pairs)
print(nums) # (1, 2, 3)
print(letters) # ('a', 'b', 'c')
Lists can serve double duty as stacks and (with the right tool) queues.
Use append() to push and pop() to pop. Both are O(1).
stack = []
# push
stack.append("page_1")
stack.append("page_2")
stack.append("page_3")
print(stack) # ['page_1', 'page_2', 'page_3']
# pop
current = stack.pop()
print(current) # page_3
print(stack) # ['page_1', 'page_2']
# peek (look without removing)
if stack:
print(stack[-1]) # page_2
# practical: bracket matching
def is_balanced(s):
stack = []
pairs = {')': '(', ']': '[', '}': '{'}
for char in s:
if char in '([{':
stack.append(char)
elif char in ')]}':
if not stack or stack[-1] != pairs[char]:
return False
stack.pop()
return len(stack) == 0
print(is_balanced("({[]})")) # True
print(is_balanced("({[}])")) # False
print(is_balanced("((())")) # False
Do not use list.pop(0) for queues — it is O(n) because every element must shift. Use collections.deque instead.
from collections import deque
queue = deque()
# enqueue
queue.append("task_1")
queue.append("task_2")
queue.append("task_3")
print(queue) # deque(['task_1', 'task_2', 'task_3'])
# dequeue
next_task = queue.popleft() # O(1)
print(next_task) # task_1
print(queue) # deque(['task_2', 'task_3'])
# practical: process tasks
while queue:
task = queue.popleft()
print(f"Processing: {task}")
# Processing: task_2
# Processing: task_3
Let us put it all together with four real-world examples.
class TodoList:
def __init__(self):
self.tasks = []
def add(self, task):
self.tasks.append({"task": task, "done": False})
print(f"Added: {task}")
def complete(self, index):
if 0 <= index < len(self.tasks):
self.tasks[index]["done"] = True
print(f"Completed: {self.tasks[index]['task']}")
else:
print("Invalid task index")
def remove(self, index):
if 0 <= index < len(self.tasks):
removed = self.tasks.pop(index)
print(f"Removed: {removed['task']}")
else:
print("Invalid task index")
def show(self):
if not self.tasks:
print("No tasks!")
return
for i, t in enumerate(self.tasks):
status = "done" if t["done"] else "pending"
print(f" {i}. [{status}] {t['task']}")
def pending(self):
return [t for t in self.tasks if not t["done"]]
def completed(self):
return [t for t in self.tasks if t["done"]]
todo = TodoList()
todo.add("Write Python tutorial")
todo.add("Review pull request")
todo.add("Deploy to production")
todo.complete(0)
todo.show()
# 0. [done] Write Python tutorial
# 1. [pending] Review pull request
# 2. [pending] Deploy to production
print(f"Pending: {len(todo.pending())}")
print(f"Completed: {len(todo.completed())}")
def print_matrix(m, label=""):
if label:
print(f"{label}:")
for row in m:
print(f" {row}")
print()
def matrix_add(a, b):
"""Add two matrices element-wise."""
return [
[a[i][j] + b[i][j] for j in range(len(a[0]))]
for i in range(len(a))
]
def matrix_transpose(m):
"""Transpose a matrix (swap rows and columns)."""
return [[row[i] for row in m] for i in range(len(m[0]))]
def matrix_multiply(a, b):
"""Multiply two matrices."""
rows_a, cols_a = len(a), len(a[0])
rows_b, cols_b = len(b), len(b[0])
assert cols_a == rows_b, "Incompatible dimensions"
return [
[sum(a[i][k] * b[k][j] for k in range(cols_a)) for j in range(cols_b)]
for i in range(rows_a)
]
# usage
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
print_matrix(matrix_add(A, B), "A + B")
# A + B:
# [6, 8]
# [10, 12]
print_matrix(matrix_transpose(A), "Transpose of A")
# Transpose of A:
# [1, 3]
# [2, 4]
print_matrix(matrix_multiply(A, B), "A x B")
# A x B:
# [19, 22]
# [43, 50]
employees = [
{"name": "Alice", "dept": "Engineering", "salary": 95000, "years": 5},
{"name": "Bob", "dept": "Marketing", "salary": 65000, "years": 3},
{"name": "Charlie", "dept": "Engineering", "salary": 110000, "years": 8},
{"name": "Diana", "dept": "Engineering", "salary": 88000, "years": 2},
{"name": "Eve", "dept": "Marketing", "salary": 72000, "years": 6},
{"name": "Frank", "dept": "Sales", "salary": 58000, "years": 1},
]
# pipeline: filter -> transform -> sort
# step 1: engineers only
engineers = [e for e in employees if e["dept"] == "Engineering"]
# step 2: add a seniority level
for eng in engineers:
eng["level"] = "Senior" if eng["years"] >= 5 else "Mid" if eng["years"] >= 3 else "Junior"
# step 3: sort by salary descending
engineers.sort(key=lambda e: e["salary"], reverse=True)
# step 4: format output
report = [
f"{e['name']} ({e['level']}) - ${e['salary']:,}"
for e in engineers
]
for line in report:
print(line)
# Charlie (Senior) - $110,000
# Alice (Senior) - $95,000
# Diana (Junior) - $88,000
# summary stats
salaries = [e["salary"] for e in engineers]
print(f"\nAverage salary: ${sum(salaries) / len(salaries):,.0f}")
print(f"Total headcount: {len(engineers)}")
class Inventory:
def __init__(self):
self.items = []
def add_item(self, name, quantity, price):
# check if item already exists
for item in self.items:
if item["name"].lower() == name.lower():
item["quantity"] += quantity
print(f"Updated {name}: quantity is now {item['quantity']}")
return
self.items.append({"name": name, "quantity": quantity, "price": price})
print(f"Added {name}: {quantity} units at ${price:.2f} each")
def remove_item(self, name, quantity=1):
for item in self.items:
if item["name"].lower() == name.lower():
if item["quantity"] >= quantity:
item["quantity"] -= quantity
if item["quantity"] == 0:
self.items.remove(item)
print(f"Removed {name} from inventory")
else:
print(f"Removed {quantity} {name}(s). Remaining: {item['quantity']}")
else:
print(f"Not enough {name} in stock (have {item['quantity']})")
return
print(f"{name} not found in inventory")
def search(self, keyword):
return [item for item in self.items if keyword.lower() in item["name"].lower()]
def total_value(self):
return sum(item["quantity"] * item["price"] for item in self.items)
def low_stock(self, threshold=5):
return [item for item in self.items if item["quantity"] <= threshold]
def report(self):
if not self.items:
print("Inventory is empty")
return
print(f"{'Item':<20} {'Qty':>5} {'Price':>8} {'Value':>10}")
print("-" * 45)
for item in sorted(self.items, key=lambda x: x["name"]):
value = item["quantity"] * item["price"]
print(f"{item['name']:<20} {item['quantity']:>5} ${item['price']:>7.2f} ${value:>9.2f}")
print("-" * 45)
print(f"{'Total':<26} {'':<8} ${self.total_value():>9.2f}")
inv = Inventory()
inv.add_item("Laptop", 10, 999.99)
inv.add_item("Mouse", 50, 29.99)
inv.add_item("Keyboard", 30, 79.99)
inv.add_item("Monitor", 3, 349.99)
inv.report()
print(f"\nLow stock items: {[i['name'] for i in inv.low_stock()]}")
These are the mistakes that trip up developers at every level. Learn them once and you will save yourself hours of debugging.
# BAD — the default list is shared across all calls
def add_item_bad(item, items=[]):
items.append(item)
return items
print(add_item_bad("a")) # ['a']
print(add_item_bad("b")) # ['a', 'b'] — not ['b']!
print(add_item_bad("c")) # ['a', 'b', 'c'] — keeps growing
# GOOD — use None as default
def add_item_good(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item_good("a")) # ['a']
print(add_item_good("b")) # ['b'] — correct!
# BAD — skips elements
numbers = [1, 2, 3, 4, 5, 6]
for num in numbers:
if num % 2 == 0:
numbers.remove(num)
print(numbers) # [1, 3, 5] — might look right, but try with [2, 4, 6, 8]
# GOOD — iterate over a copy
numbers = [1, 2, 3, 4, 5, 6]
for num in numbers[:]:
if num % 2 == 0:
numbers.remove(num)
print(numbers) # [1, 3, 5]
# BEST — use a list comprehension
numbers = [1, 2, 3, 4, 5, 6]
numbers = [num for num in numbers if num % 2 != 0]
print(numbers) # [1, 3, 5]
import copy # shallow copy does NOT copy nested objects original = [[1, 2], [3, 4]] shallow = original.copy() shallow[0][0] = 99 print(original) # [[99, 2], [3, 4]] — original is affected! # always use deepcopy for nested structures original = [[1, 2], [3, 4]] deep = copy.deepcopy(original) deep[0][0] = 99 print(original) # [[1, 2], [3, 4]] — safe
# BAD — all rows share the same inner list grid = [[0] * 3] * 3 grid[0][0] = 1 print(grid) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] # GOOD — use a comprehension grid = [[0] * 3 for _ in range(3)] grid[0][0] = 1 print(grid) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
Follow these habits and your list code will be clean, fast, and easy to maintain.
# 1. Prefer list comprehensions over manual loops
# bad
result = []
for x in range(10):
if x % 2 == 0:
result.append(x ** 2)
# good
result = [x ** 2 for x in range(10) if x % 2 == 0]
# 2. Use enumerate() instead of range(len(...))
# bad
for i in range(len(items)):
print(i, items[i])
# good
for i, item in enumerate(items):
print(i, item)
# 3. Use zip() to iterate over multiple lists
# bad
for i in range(len(names)):
print(names[i], scores[i])
# good
for name, score in zip(names, scores):
print(name, score)
# 4. Use unpacking
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
# 5. Prefer 'in' over index checking
# bad
found = False
for item in items:
if item == target:
found = True
break
# good
found = target in items
# 6. Use list.sort() when you do not need the original
# sort() is slightly faster than sorted() because it does not create a new list
data = [3, 1, 4, 1, 5]
data.sort() # in-place, no new list created
# 7. Use collections.deque for queue operations
from collections import deque
queue = deque() # O(1) append and popleft
[start:stop:step] and you will use it everywhere.copy.deepcopy() for nested structures.None as the default and create the list inside the function.key parameter with lambdas for custom sort orders.collections.deque instead.Lists are fundamental to Python programming. Master them and you have a solid foundation for everything else — from data processing to algorithms to web development. Practice the examples in this tutorial, experiment with variations, and you will have list fluency in no time.