Copying a list sounds like the simplest operation in Python, but it is also the source of one of the most persistent beginner bugs. If you write new_list = old_list and then modify new_list, the changes appear in old_list too, because both names point to the exact same list object in memory. This is not a bug in Python; it is a consequence of how assignment works in the language. Assignment binds a name to an object, and it never creates a copy. If you want an independent list that you can modify without affecting the original, you must explicitly create a copy using one of the techniques covered in this article.
Before diving into the how, it helps to understand why copying matters. Lists are mutable, which means you can change their contents after creation. That mutability is useful, but it also means that sharing a list between two parts of your program can lead to unintended side effects. One function modifies what it thinks is a private list, and another function sees its data change unexpectedly. Making a defensive copy at the boundary between components is a common pattern that prevents these cross-contamination bugs. If you have worked through the articles on working with Python lists and modifying Python lists, you already understand mutability and in-place modification. Copying is the tool that lets you control that mutability.
The assignment trap: why equals does not copy
When you write new_list = old_list, Python does exactly one thing: it makes the name new_list refer to the same object that old_list refers to. Nothing is copied, no new list is created, and no memory is allocated for duplicate data. This is called aliasing, and it means that any modification through either name is visible through both.
original = [1, 2, 3]
alias = original
alias.append(4)After this code runs, original contains [1, 2, 3, 4] even though you never called append on original directly. The append went through the alias name, but both names point to the same single list object. To confirm that two names refer to the same object, use the is operator. The expression original is alias returns True when they share memory, and True is exactly the warning sign that you are working with one list, not two.
The assignment trap is not limited to simple variable assignment. Passing a list to a function, storing it in a dictionary, or appending it to another list all create references, not copies. Every name and every container slot is a reference to an object, and you only get a new object when you explicitly ask for one.
Three ways to make a shallow copy
Python provides three equivalent ways to create a shallow copy of a list. All three produce a new list object whose top-level elements are the same references as those in the original. For a flat list of immutable values like numbers, strings, or tuples, a shallow copy is completely independent. Modifying the copy will not affect the original, and vice versa.
The slice syntax with an omitted start and stop index creates a new list containing every element from the original in order. This is the most common Python idiom for copying a list and works on any sequence type, not just lists.
original = [10, 20, 30]
copy = original[:]
copy[0] = 99After this code, original is still [10, 20, 30] and copy is [99, 20, 30]. The slice created a new list, so the index assignment on copy only affected copy.
The copy method, added in Python 3.3, reads more explicitly as an intention to copy. It returns a new list with the same elements in the same order. Many Python programmers prefer the copy method over slicing because the word copy makes the intent clearer to anyone reading the code later.
The list constructor, called with the original list as its argument, also creates a shallow copy. It works because the list constructor accepts any iterable and builds a new list from its items. If you pass an existing list, the constructor iterates over it and populates a fresh list with the same references.
When shallow copies are not enough
A shallow copy creates a new outer list but reuses the references to the inner objects. For a flat list of numbers or strings, this is exactly what you want because numbers and strings are immutable. Changing an integer in the copy assigns a new integer reference to that slot without affecting the original slot, which still holds the old reference.
The problem arises with nested mutable objects. If your list contains inner lists, dictionaries, sets, or custom mutable objects, a shallow copy shares those inner objects with the original. Modifying an inner list through the copy also modifies it through the original, because both outer lists point to the same inner list object.
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0].append(99)After this code, original[0] is [1, 2, 99] because shallow[0] and original[0] are the same inner list. The outer lists are independent, but their contents point to shared nested objects. This is the exact scenario that trips up beginners who think they have made a fully independent copy.
Deep copies for nested structures
The copy module in Python's standard library provides a deepcopy function that solves the nested object problem. A deep copy recursively walks through every level of nesting and creates copies of every mutable object it finds. The result is a completely independent clone where no mutable object is shared between the original and the copy.
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0].append(99)Now original[0] stays as [1, 2] while deep[0] becomes [1, 2, 99]. The deep copy created new inner lists, so modifications to the inner structure of the copy do not propagate to the original. This independence comes at a cost: deepcopy is slower than a shallow copy because it must traverse every nested object, and it uses more memory because every level is duplicated.
Deep copy is the right tool when your list contains nested mutable structures and you need true independence at every level. For configuration data that will be modified, for game state that evolves independently, or for any scenario where shared mutable state would cause bugs, reach for deepcopy. For flat lists of immutable values, a shallow copy is not just sufficient but more efficient.
The article on nested lists in Python explores multi-dimensional list structures in detail, and the concepts of shallow versus deep copying apply directly to those nested arrangements.
Rune AI
Key Insights
- Plain assignment does not copy a list; it creates an alias that shares the same object.
- Three shallow copy techniques produce independent top-level lists: slicing, the copy method, and the list constructor.
- A shallow copy shares references to nested objects; modifying an inner list in the copy also affects the original.
- The deepcopy function from the copy module recursively copies every level of nesting.
- Use shallow copies for flat lists of immutable items; use deep copies for nested mutable structures.
Frequently Asked Questions
Why does changing a copied list sometimes change the original?
What is the difference between a shallow copy and a deep copy?
Which copy method should I use for a simple list of numbers or strings?
Conclusion
Copying lists correctly is a skill that prevents an entire category of hard-to-debug errors. Learn the three shallow copy techniques for everyday use, recognize when nested structures demand a deep copy, and never use plain assignment when you need independent data.
More in this topic
Define and Call Python Functions
Learn the exact syntax for defining and calling Python functions with the def keyword, including naming rules, the function body, and how the call stack works.
Python Function Parameters and Arguments
Learn the difference between parameters and arguments, how to pass values by position and by keyword, and the rules for default parameter values.
Python Functions Explained
Learn what Python functions are, why they matter for organizing code, and how they form the foundation of non-trivial programs.