Technology  /  Python

๐Ÿ Python 78 guides ยท updated 2026

From first variable to OOP, generators, and real projects โ€” the language that runs everything from data pipelines to AI agents, taught the practical way.

Python Lists as a Collection: Mutability, Nesting, and Common Operations

A Python list is an ordered, mutable collection that can hold any mix of types. It is the most commonly used data structure in Python โ€” and also one that gets misused when developers reach for it by habit rather than by choice.

Creating Lists

# Direct literal
fruits = ["apple", "banana", "cherry"]
# List constructor from any iterable
numbers = list(range(1, 6)) # [1, 2, 3, 4, 5]
chars = list("Python") # ['P', 'y', 't', 'h', 'o', 'n']
from_tuple = list((10, 20, 30))
# Repeat a pattern
zeros = [0] * 5 # [0, 0, 0, 0, 0]
# List comprehension
squares = [x**2 for x in range(1, 6)] # [1, 4, 9, 16, 25]
evens = [x for x in range(20) if x % 2 == 0]

Mutable Operations

Lists support in-place modification. This is their primary advantage over tuples.

items = [3, 1, 4, 1, 5, 9]
# Add elements
items.append(2) # adds to end: [3, 1, 4, 1, 5, 9, 2]
items.insert(0, 0) # insert at index 0: [0, 3, 1, 4, 1, 5, 9, 2]
items.extend([6, 5, 3]) # add multiple: [..., 6, 5, 3]
# Remove elements
items.remove(1) # removes first occurrence of 1
popped = items.pop() # removes and returns last element
popped_at = items.pop(2) # removes and returns element at index 2
del items[0] # removes element at index 0
# Find and count
print(items.index(5)) # index of first occurrence of 5
print(items.count(3)) # how many times 3 appears

sort() vs sorted() โ€” Knowing the Difference

This is a common source of bugs:

numbers = [5, 2, 9, 1, 7, 3]
# sort() modifies the list IN PLACE and returns None
numbers.sort()
print(numbers) # [1, 2, 3, 5, 7, 9]
# sorted() returns a NEW sorted list โ€” original unchanged
original = [5, 2, 9, 1, 7, 3]
new_sorted = sorted(original)
print(original) # [5, 2, 9, 1, 7, 3] โ€” unchanged
print(new_sorted) # [1, 2, 3, 5, 7, 9]
# Sort by a key
words = ["banana", "apple", "fig", "cherry"]
words.sort(key=len) # sort by string length
print(words) # ['fig', 'apple', 'banana', 'cherry']
# Reverse sort
numbers.sort(reverse=True)
print(numbers) # [9, 7, 5, 3, 2, 1]
# Common mistake: assigning sort() to a variable
result = numbers.sort() # result is None!

Nested Lists (Matrices and Tables)

# 3ร—3 matrix
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
# Access element at row 1, column 2
print(matrix[1][2]) # 6
# Transpose: swap rows and columns
transposed = [[matrix[j][i] for j in range(3)] for i in range(3)]
print(transposed)
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# Warning: avoid list multiplication for nested lists
wrong = [[0] * 3] * 3 # all three rows are the SAME object
wrong[0][0] = 99
print(wrong) # [[99, 0, 0], [99, 0, 0], [99, 0, 0]] โ€” unexpected!
correct = [[0] * 3 for _ in range(3)] # each row is a separate list
correct[0][0] = 99
print(correct) # [[99, 0, 0], [0, 0, 0], [0, 0, 0]]

The [[0] * 3] * 3 trap is one of the most common Python bugs with nested lists.

List Comprehension vs map()

Both transform a list โ€” comprehensions are generally preferred for readability:

numbers = [1, 2, 3, 4, 5]
# List comprehension
squared = [x**2 for x in numbers]
# map() โ€” functional style, returns an iterator
squared_map = list(map(lambda x: x**2, numbers))
# Both produce [1, 4, 9, 16, 25]
# Comprehension is more Pythonic for simple transforms
# map() can be useful when the function already exists
import math
roots = list(map(math.sqrt, numbers)) # cleaner than [math.sqrt(x) for x in ...]
# With filter
evens = [x for x in numbers if x % 2 == 0]
evens_filter = list(filter(lambda x: x % 2 == 0, numbers))

Useful Built-in Functions with Lists

data = [10, 50, 75, 83, 98, 84, 32, 10]
print(min(data)) # 10
print(max(data)) # 98
print(sum(data)) # 442
print(len(data)) # 8
print(sum(data) / len(data)) # 55.25 (mean)
# enumerate โ€” iterate with index
for i, value in enumerate(data, start=1):
print(f"{i}: {value}")
# zip โ€” iterate multiple lists together
names = ["Alice", "Bob", "Carol"]
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# any and all
print(any(x > 90 for x in scores)) # True โ€” at least one
print(all(x >= 70 for x in scores)) # True โ€” all pass

Memory Consideration: Lists Grow Dynamically

Python lists over-allocate memory to amortise the cost of append(). When you create a list with a known final size, allocating it up front (via comprehension or [None] * n) is slightly more efficient than repeated append() calls:

import sys
# Growing list via append
lst = []
for i in range(10):
lst.append(i)
print(f"len={len(lst)}, size={sys.getsizeof(lst)} bytes")
# Pre-allocated via comprehension โ€” consistent memory from the start
lst2 = [i for i in range(10)]

For large lists in performance-critical code, this matters. For most application code, append() is fine.