You're building a 2D matrix for a competitive programming problem. Quick and confident, you write:

python
grid = [[]] * 3
Three empty rows, ready to hold your data. You update the first row:
python
grid[0].append(1)
You print the result expecting [[1], [], []]. Instead, Python hands you this:
python
print(grid) # [[1], [1], [1]]
Every row changed. Not just the one you touched. If you've hit this wall, you've run straight into the python nested list multiplication trap, one of the most common and most confusing bugs for developers moving from simple lists to nested data structures. It looks like a Python bug. It isn't. It's a direct consequence of how Python handles object references, and once you understand the mechanism, you'll never fall for it again. Let's break down exactly why this happens and how to build nested lists the right way.
The Problem Code: A Bug That Looks Like Magic (In a Bad Way)
Here's the trap in its purest form:

python
grid = [[]] * 3
grid[0].append(1)
print(grid) # Output: [[1], [1], [1]] -> Unexpected!
At first glance, this looks completely reasonable. [[]] * 3 should mean "three empty lists," right? And grid[0].append(1) should only affect the first one.
But Python doesn't see three lists. It sees one list, referenced three times.
This isn't a one-off quirk either; it's common enough that it has its own long-standing entry in the official Python FAQ, and it shows up repeatedly in Python's own issue tracker. Reports like bpo-27135 ("nested list produced with multiplication is linked to the same list"), bpo-45169 ("shallow copy occurs when list multiplication is used to create nested lists; can confuse users"), and bpo-25975 ("weird multiplication") were all filed by developers who ran into this exact behavior and assumed, reasonably, that they'd found a bug.
Every one of them was closed as "not a bug" the behavior is intentional and documented, just deeply unintuitive.
The Root Cause: Object References and the Python List Multiplication Trap
To understand why this happens, you need to understand what a Python list actually stores. This is where most explanations get hand-wavy. Let's fix that.
Variables Are Labels, Not Boxes
In many beginner-friendly explanations, variables are described as "boxes" that hold values. That mental model breaks down fast in Python. A more accurate picture: variables are labels (or pointers) attached to objects living in memory. When you write x = [], Python creates an empty list object somewhere in memory, and x becomes a name that points to it.
This matters enormously when you're dealing with nested structures.
What [[]] * n Actually Does

When you write [[]], Python creates:
One empty inner list object (let's call its memory address 0xA1).
One outer list containing a single reference to that object.
Now here's the critical part. When you multiply that outer list by n, Python does not go back and create new inner list objects. It simply repeats the existing reference n times inside the outer list. The * operator duplicates the pointer, not the object.
This is the essence of a python shallow copy vs deep copy list problem. A shallow copy duplicates the top-level container but leaves nested objects shared. [[]] * n is effectively a shallow duplication of a single reference it was never designed to clone what that reference points to.
Classic references on this trap, including the old Python Cookbook recipe "Creating Lists of Lists Without Sharing References," break the process down into two conceptual steps to make it click:
python
row = [0] * 5 # one list, five references to the immutable value 0
multi = [row] * 3 # one outer list, three references to the SAME row object
Seen this way, multi[0][0] = 'Changed!' isn't editing "row 0" it's editing the only row object that exists, and all three names in multi are watching it.
Why It Only Bites You With Mutable Elements
This is the detail that trips up even experienced developers: [0] * 5 is perfectly safe, but [[0]] * 5 is not. The difference isn't the multiplication, it's whether the repeated element is mutable.
python
nums = [0] * 3
nums[0] = 1
print(nums) # [1, 0, 0] -> totally fine
Here, nums[0] = 1 doesn't mutate the integer 0 in place (you can't make integers immutable). It rebinds index 0 to point at a brand-new integer object, leaving the other two slots pointing at the original. Nothing is shared after the rebind, so nothing looks broken.
Contrast that with a mutable element like a list or dictionary:
python
grid = [[0]] * 3
grid[0][0] = 1 # this MUTATES the shared inner list in place
print(grid) # [[1], [1], [1]]

grid[0][0] = 1 doesn't rebind grid[0] to a new list it reaches inside the existing shared list and changes it in place. Since all three slots in the grid are names for that same object, the mutation is visible through every one of them. The same shared-reference risk applies to dictionaries and sets used as the repeated element, e.g. [{}] * 3 or [set()] * 3 any mutable container will exhibit the exact same behavior.
Proving It with id()
Don't take this on faith, verify it yourself. Python's built-in id() function returns the memory address of an object, which is the fastest way to expose a python list multiplication reference issue:
python
grid = [[]] * 3
print(id(grid[0])) # e.g., 140234567891200
print(id(grid[1])) # e.g., 140234567891200
print(id(grid[2])) # e.g., 140234567891200
All three IDs are identical. grid[0], grid[1], and grid[2] are not three different lists that happen to look the same; they are three names for the exact same object. Calling .append() on grid[0] mutates that one shared object, and since every "row" is just another label for it, all three appear to change at once. This is a textbook python matrix initialization bug, and it's why using [[]] * n for a matrix or 2D grid is almost always wrong.
As a debugging habit: whenever a value changes somewhere you didn't expect, don't start by second-guessing your logic check id() on the objects involved first. If two variables that should be independent return the same id(), you've found a shared-reference bug in seconds instead of hours.
The Correct Solutions to Create Independent Nested Lists in Python
Now for the fix. The goal is simple: instead of copying a reference to one list, you need to create n genuinely separate list objects.
Solution 1 (Recommended): List Comprehension
The cleanest, most Pythonic way to create independent nested lists in Python is a list comprehension:
python
grid = [[] for _ in range(n)]

Why this works, and [[]] * n doesn't: a list comprehension executes the expression [] fresh, on every single iteration of the loop. Each pass through range(n) triggers a brand-new call that constructs a brand-new empty list object, with its own unique memory address. There's no shared reference anywhere in sight.
Verify it the same way as before:
python
grid = [[] for _ in range(3)]
print(id(grid[0]), id(grid[1]), id(grid[2]))
# Three completely different addresses
Now mutating one row leaves the others untouched:
Python
grid[0].append(1)
print(grid) # [[1], [], []] -> Exactly as expected
This pattern is also the standard, idiomatic approach to any python list comprehension nested list initialization whether you're building a matrix, a grid for a grid-based algorithm, or a list of buckets for a hashing exercise.
Solution 2: Explicit Loops or the copy Module
If you prefer explicit, step-by-step logic (or you're teaching this concept to beginners), a plain for loop achieves the same result:
Python
grid = []
for _ in range(n):
grid.append([])
Each loop iteration calls [] again, exactly like the list comprehension does, generating a fresh object every time.
Alternatively, if you already have one populated list and want independent copies of it, Python's copy module offers deepcopy():
Python
import copy
template = [1, 2, 3]
grid = [copy.deepcopy(template) for _ in range(n)]
copy.deepcopy() recursively clones an object and everything it contains, guaranteeing no shared references anywhere in the structure. This matters most when your inner lists aren't empty or contain other mutable objects (like nested lists or dictionaries) a shallow copy.copy() or slicing (template[:]) still shares references to any nested mutable objects inside. In fact, even list.copy() on a list of lists is only a shallow copy: it creates a new outer list, but every nested mutable element inside it is still the same shared object. Full independence at every depth requires copy.deepcopy() or a comprehension.
The Trap Hiding Inside the Fix
A list comprehension only guarantees a fresh outer object on each iteration it doesn't automatically make everything inside safe if you reuse an already-created mutable object from outside the loop:
Python
shared_row = []
grid = [[shared_row] for _ in range(3)]
grid[0][0].append(1)
print(grid) # [[[1]], [[1]], [[1]]]
Here, the comprehension does create three independent outer lists but each one holds a reference to the same pre-existing shared_row object, so mutating it through any slot shows up everywhere. The fix is the same underlying principle: make sure the mutable object itself is freshly constructed inside the loop, not captured from outside it.
When Shared References Are Actually What You Want
The reference-repeating behavior of * isn't purely a footgun it's occasionally exactly the tool for the job:
Immutable repeated elements numbers, strings, tuples are always safe with *, since there's nothing to mutate in place; any "change" just rebinds a slot to a new object.
Read-only lookup tables or sentinel values. Repeating a reference to a shared, never-mutated configuration object or a shared None placeholder n times is a memory-efficient, intentional use of the same mechanism.
Deliberate shared mutable state, such as several slots that should all reflect updates to one shared cache object, can also legitimately rely on this. In that case the "bug" is the desired behavior as long as it's intentional and clearly documented, since the next person reading the code will otherwise assume it's a mistake.
How This Fits Into a Bigger Family of Python Reference Bugs

This trap is not an isolated Python weirdness; it belongs to a well-known family of bugs that all come from the same root cause: confusing "creating a new object" with "handing out another reference to an existing one." The most notorious relative is the mutable default argument bug:
Python
def add_item(item, bucket=[]):
bucket.append(item)
return bucket
Because the default list [] is created exactly once, when the function is defined, every call that relies on the default shares and mutates the same list object an outcome with the identical underlying cause as [[]] * n. Community resources like the widely-referenced wtfpython repository catalog this bug and the list-multiplication trap side by side as two faces of the same reference-vs-copy confusion, alongside issues like shallow .copy() not protecting nested mutable elements.
For large numeric 2D structures, it's also worth knowing that this entire class of bug doesn't apply to NumPy: np.zeros((rows, cols)) allocates one contiguous, independent block of memory per array rather than a Python list of object references, so there's no shared-row trap to worry about. If your matrix is purely numeric, that's often a better tool than nested Python lists to begin with.
Key Takeaways: Best Practices for Nested Lists in Python
Never use [[]] * n to initialize a matrix or nested list it creates n references to a single shared object, not n independent lists.
Use [[] for _ in range(n)] as your default, idiomatic solution. It evaluates [] fresh on every iteration, giving you truly independent nested lists.
Reach for copy.deepcopy() when you need to duplicate an already-populated nested structure without any shared references at any depth.
Multiplication is safe for immutable elements (numbers, strings, tuples) but risky for any mutable one (lists, dicts, sets) the mutability of the repeated element is what decides whether * is safe.
A comprehension only protects the outer layer. If the inner value is a mutable object built outside the loop and merely referenced inside it, it's still shared and built fresh inside the loop instead.
Remember the sibling bug: mutable default function arguments (def f(x=[])) fail for exactly the same reference-sharing reason.
Conclusion
The python nested list multiplication trap is a rite of passage for anyone working with 2D data structures in Python matrices, grids, boards, adjacency lists, you name it. The bug isn't a quirk or an inconsistency in the language, and it isn't even considered a bug by Python's own maintainers, as multiple closed issue-tracker reports confirm it's a direct, logical consequence of how Python handles object references versus object creation.
Once you internalize that * duplicates pointers while a comprehension or loop creates new objects, you'll spot this trap instantly in code review, recognize its relatives like mutable default arguments, and you'll never lose an afternoon debugging mysteriously synchronized rows again.
Have you been bitten by this trap before? Drop your favorite (or most painful) Python gotcha in the comments below. We're always collecting them for the next deep dive.