Skip to content

list Copying

Understanding copy semantics is essential when working with mutable lists.


The Aliasing Problem

Assignment creates a reference, not a copy:

a = [1, 2, 3]
b = a           # b points to same object
a.append(4)
print(b)        # [1, 2, 3, 4]
a ─┬──► [1, 2, 3, 4]
   │
b ─┘

Creating Copies

Method 1: Slicing

a = [1, 2, 3]
b = a[:]        # Shallow copy
a.append(4)
print(b)        # [1, 2, 3]

Method 2: copy() method

a = [1, 2, 3]
b = a.copy()    # Shallow copy

Method 3: list() constructor

a = [1, 2, 3]
b = list(a)     # Shallow copy

Shallow vs Deep Copy

Shallow Copy

Copies the list structure, but nested objects are still shared:

a = [[1, 2], [3, 4]]
b = a.copy()          # Shallow copy

a[0][0] = 'X'
print(b)              # [['X', 2], [3, 4]]  (affected!)

a[0] = [9, 9]
print(b)              # [['X', 2], [3, 4]]  (not affected)
a ──► [ ref1, ref2 ]
         │      │
b ──► [ ref1, ref2 ]   (same inner refs)
         │      │
         ▼      ▼
      [1,2]  [3,4]

Deep Copy

Recursively copies all nested objects:

import copy

a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)  # Deep copy

a[0][0] = 'X'
print(b)              # [[1, 2], [3, 4]]  (unchanged)

+ vs += Behavior

+ Creates New Object

a = [1, 2, 3]
print(id(a))          # e.g., 140234567890

a = a + [4]
print(id(a))          # Different id (new object)

+= Modifies In-Place

a = [1, 2, 3]
print(id(a))          # e.g., 140234567890

a += [4]
print(id(a))          # Same id (same object)

This matters when other names reference the same list:

a = [1, 2, 3]
b = a

a = a + [4]           # a is new object
print(b)              # [1, 2, 3] (b unchanged)

a = [1, 2, 3]
b = a

a += [4]              # a modified in-place
print(b)              # [1, 2, 3, 4] (b affected!)

When to Use Which

Scenario Method
Simple list of immutables a[:] or a.copy()
Nested lists/objects copy.deepcopy(a)
Independent modification Always copy first

Common Pitfall: Iterating While Modifying

# Bug: skips elements
a = [1, 2, 3, 4]
for x in a:
    if x % 2 == 0:
        a.remove(x)
print(a)  # [1, 3] — but 4 was skipped!

# Fix: iterate over a copy
a = [1, 2, 3, 4]
for x in a[:]:        # a[:] is a copy
    if x % 2 == 0:
        a.remove(x)
print(a)  # [1, 3]

# Better: use list comprehension
a = [x for x in a if x % 2 != 0]

Summary

Method Shallow Deep New Object
b = a ❌ (alias)
b = a[:]
b = a.copy()
b = list(a)
b = copy.deepcopy(a)