Python Copy List: Assignment, copy.copy(), and deepcopy()

Learn how to copy or clone a Python list using assignment, shallow copy.copy(), list slicing, list.copy(), and copy.deepcopy(), with examples that show when nested objects are shared or independent.

Published

Updated

Read time 7 min read

Reviewed byDeepak Prasad

Python Copy List: Assignment, copy.copy(), and deepcopy()

If you need a second list that starts equal to an existing one, the mechanism matters: plain assignment shares one object, a shallow copy gives a new list whose elements still point at the same inner objects, and a deep copy duplicates nested mutable data as well. This tutorial walks through those behaviors with short scripts you can run locally or with the in-page Run control where it appears. For list basics, see Python list.

The flow is assignment first, then the copy module, shallow copies (including slices), nested mutables, deepcopy, and a comparison table before the summary.

Tested on: Python 3.13.3; kernel 6.14.0-37-generic.


Copy a list with assignment (=)

In Python, new_list = old_list does not allocate a new list; it binds new_list to the same object as old_list. You can confirm that with id(old_list) == id(new_list).

Example: assignment shows the same object

Start from a small list and bind a second name to it, then compare identities so the output makes the sharing explicit.

python
my_list = [1, 2, 3, 4]
print("my_list contains:", my_list)

new_list = my_list
print("new_list contains:", new_list)

if id(my_list) == id(new_list):
    print("Both names refer to the same list (same id).")
else:
    print("Different ids.")
Output

Run it and you should see the same printed sequence for both names, then the message that both names share the same id.

Example: appending after assignment affects both names

Here the two names still point at one list, so an in-place method on either name updates the single underlying object.

python
my_list = [1, 2, 3, 4]
print("my_list:", my_list)

new_list = my_list
print("new_list:", new_list, "\n")

print("Appending to my_list")
my_list.append("a")

print("my_list after append:", my_list)
print("new_list after append:", new_list, "\n")

if id(my_list) == id(new_list):
    print("Same id: one list, two names.")
Output

Appending to my_list also changes new_list, because they reference one list. That is the same idea as other in-place list changes such as removing elements from a list: every name bound to that object sees the update.

Example: mutating through one name updates the shared list

Assigning through a subscript on new_list still mutates that shared list, so my_list reflects the same index change.

python
my_list = [1, 2, 3, 4]
print("my_list:", my_list)

new_list = my_list
print("new_list:", new_list, "\n")

print("Assigning new_list[1] = 'TEST'")
new_list[1] = "TEST"

print("my_list after index assign:", my_list)
print("new_list after index assign:", new_list, "\n")

if id(my_list) == id(new_list):
    print("Same id: index assignment mutates the shared list.")
Output

Replacing an element through new_list changes my_list as well, because both names point at the same list object.


The copy module

The standard library copy module supports shallow and deep copies of compound objects such as lists, tuples, dicts, and many user-defined types.

This small program builds a list that contains another list, takes a shallow and a deep copy, then mutates the inner list through the original. Watch how shallow follows the inner mutation while deep does not.

python
import copy

sample = [[1], 2]
shallow = copy.copy(sample)
deep = copy.deepcopy(sample)
sample[0].append(3)
print("after mutating inner list via sample:", sample)
print("shallow sees same inner list:", shallow)
print("deep stays independent:", deep)
Output

The last line should still show [[1], 2] for deep while sample and shallow show the inner list grown to [1, 3].

For lists you can often use list.copy() or a full slice [:] instead of copy.copy when you only need a shallow duplicate of a list.

Shallow and deep copy are not meant for every type: modules, class objects, functions, methods, tracebacks, stack frames, sockets, and similar objects are not copied in the usual sense. When a copy cannot be performed, the module raises copy.Error.


Shallow copy with copy.copy

A shallow copy constructs a new collection object and inserts references to the items found in the original. Top-level list slots are rehomed in a new list, but nested mutable objects (for example a dict inside the list) are not duplicated: both lists refer to the same inner object.

Example: shallow copy gets a new list id

For a flat list of immutables, copy.copy produces a new list object with the same values, so the two id values differ even though the printed contents match.

python
import copy

my_list = [1, 2, 3, 4]
print("my_list:", my_list)

new_list = copy.copy(my_list)
# Equivalent for lists: new_list = my_list[:] or new_list = my_list.copy()

print("new_list:", new_list, "\n")

if id(my_list) == id(new_list):
    print("Same id.")
else:
    print("Different ids: new top-level list object.")
Output

You should see matching values, then the “Different ids” branch, because copy.copy allocated a new list container.

Example: changing a top-level element in one list does not change the other

Replacing one slot rebinds only that slot in new_list; integers are immutable, so the other list’s elements are untouched.

python
import copy

my_list = [1, 2, 3, 4]
print("my_list:", my_list)

new_list = copy.copy(my_list)
print("new_list:", new_list, "\n")

print("Assign new_list[1] = 'TEST'")
new_list[1] = "TEST"

print("my_list after assign:", my_list)
print("new_list after assign:", new_list, "\n")

if id(my_list) == id(new_list):
    print("Same id.")
else:
    print("Different ids.")
Output

Here my_list stays [1, 2, 3, 4] with only the second slot in new_list changed, because replacing an integer in one list does not touch the other list’s slots. Appending to one list would likewise not append to the other, as long as you only mutate the list objects themselves, not shared nested objects. For more list APIs, see Python list pop() examples.

Example: nested dicts are still shared after a shallow copy

The outer list is new, but the first element is still the same dict object in memory, so in-place key updates on one list show up on the other.

python
import copy

my_list = [{"car": "maruti"}, 2, "apple"]
new_list = copy.copy(my_list)

print("Append to my_list")
my_list.append("TEST")

print("my_list:", my_list)
print("new_list:", new_list, "\n")

print("Mutate nested dict in my_list")
my_list[0]["car"] = "honda"

print("my_list:", my_list)
print("new_list:", new_list, "\n")

if id(my_list) == id(new_list):
    print("Same top-level list id.")
else:
    print("Different top-level list ids; check nested dict still shared.")
Output

The append only affects my_list, but changing my_list[0]["car"] also changes new_list[0]["car"], because the shallow copy reuses the same dict instance in both lists.


Deep copy with copy.deepcopy

copy.deepcopy walks the object graph and duplicates mutable objects recursively (with cycle detection and user hooks documented in the library reference). It is slower than a shallow copy but is the right tool when you need fully independent nested structures.

Example: deepcopy yields a new list with different inner dict ids

After deepcopy, the outer list and the nested dict should both be fresh objects, which you can verify with id on the lists and is on the dicts.

python
import copy

my_list = [{"car": "maruti"}, 2, "apple"]
print("my_list:", my_list)

new_list = copy.deepcopy(my_list)
print("new_list:", new_list, "\n")

if id(my_list) == id(new_list):
    print("Same list id.")
else:
    print("Different list ids.")

print("Same nested dict object?", my_list[0] is new_list[0])
Output

You should see different list ids and Same nested dict object? False, meaning the dict was duplicated, not aliased.

Example: mutating a nested dict after deepcopy does not leak

Changing the nested mapping on my_list must not alter new_list, because deepcopy stopped the two trees from sharing that dict.

python
import copy

my_list = [{"car": "maruti"}, 2, "apple"]
print("my_list:", my_list)

new_list = copy.deepcopy(my_list)
print("new_list:", new_list, "\n")

print("Mutate nested dict in my_list only")
my_list[0]["car"] = "honda"

print("my_list:", my_list)
print("new_list:", new_list)
Output

new_list should still show maruti for the nested car field, while my_list shows honda, because the deep copy detached the nested dict.


Assignment vs shallow copy vs deepcopy

Use this table as a quick mental model (see also Python functions for how you might wrap copy logic in reusable helpers):

Behavior Assignment (=) Shallow (copy.copy, [:], list.copy) deepcopy
New top-level list object No Yes Yes
Same items at first level Same object Same references as original New objects where the copier duplicates mutables
Nested mutable objects (e.g. dict in list) Shared Shared Independent copies (by default)
Typical use Another name for the same list Duplicate a flat or immutable-heavy list Duplicate trees with nested lists or dicts

Summary

Assignment binds a new name to an existing list; mutations are visible through every alias. A shallow copy creates a new list but reuses references to the original items, so nested mutables stay linked unless you replace the item in one list with a different object. copy.deepcopy duplicates nested mutable structure so two list values can evolve independently. For flat lists of immutable values such as numbers and strings, shallow techniques are enough and cheaper; when in doubt about nesting, prefer deepcopy or restructure data so you do not need a full graph copy.


References


Frequently Asked Questions

1. Does using = copy a list in Python?

No. For lists, name = other_list binds another name to the same list object; both names see the same id and the same mutations.

2. What is the difference between shallow copy and deep copy for a list?

A shallow copy builds a new list and inserts references to the same items as the original, so nested mutable objects stay shared. A deep copy recursively copies nested mutable objects so the new structure is independent.

3. How do you shallow copy a list in Python?

Use copy.copy(my_list), my_list.copy(), my_list[:], or list(my_list) for a shallow copy; all create a new top-level list for a list instance.

4. When should you use copy.deepcopy on a list?

Use deepcopy when the list may contain nested dicts, lists, sets, or other mutable objects and you need changes in one tree not to affect the other.

5. Is copy.deepcopy slower than copy.copy?

Usually yes, because deepcopy walks the object graph and duplicates mutable nodes; prefer shallow copies when you know there are no nested mutables you need to isolate.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …