Python Copying Lists

In the last lesson you learned Sorting Lists, where you put items in order. Now you want to take a list and make a separate copy of it. Maybe you want to change the copy and keep the original safe. That sounds simple. But this is the one place where almost everybody gets surprised. So let’s go slow and get it right.

🤔 Why Copying Lists Trips People Up

Here is the pain. You have a list. You write new_list = old_list because that looks like copying. Then you change new_list, and somehow old_list changes too. You never touched old_list at all. What happened?

The fix is simple once you see it. Assigning a list with = does not make a copy. It just gives the same list a second name. To make a real, separate copy you use one of a few small tools. This lesson shows you each one.

🧩 What ”=” Really Does

When you do new_list = old_list, Python does not build a new list. It makes new_list point at the same list that old_list already points at. So now you have two names, but only one list behind them.

Think of it like a shared document. Alex shares a Google Doc with Riya. Now there are two links, but it is still one document. If Riya edits it, Alex sees the change. They are looking at the same file. Two links, one document. That is exactly what = does with a list.

This idea has a name. When two names point at the same list, we call it aliasing. The two names are aliases for one list.

Let’s see the surprise with our own eyes. This code makes a list, gives it a second name, changes the second name, then prints both.

old_list = ["apple", "banana", "cherry"]
new_list = old_list
new_list.append("mango")
print("old_list:", old_list)
print("new_list:", new_list)

Output

old_list: ['apple', 'banana', 'cherry', 'mango']
new_list: ['apple', 'banana', 'cherry', 'mango']

See that? We only called .append() on new_list. But old_list got "mango" too. That is because both names point at the one list. There was never a second list to keep safe.

Note

You can check this yourself with the is keyword. new_list is old_list returns True when both names point at the same list. After a real copy, it returns False.

🧩 Making a Real Copy

To actually copy a list, you ask Python to build a brand new list with the same items inside. Then you have two separate lists. Changing one leaves the other alone.

A few common ways all do the same job for a simple list. Pick whichever reads best to you.

The .copy() method is the clearest one to read. It plainly says “copy.”

old_list = ["apple", "banana", "cherry"]
new_list = old_list.copy()
new_list.append("mango")
print("old_list:", old_list)
print("new_list:", new_list)

Output

old_list: ['apple', 'banana', 'cherry']
new_list: ['apple', 'banana', 'cherry', 'mango']

Now old_list stayed the same and only new_list changed. That is what we wanted all along.

The slice trick [:] does the same thing. A slice with no start and no end means “give me everything,” and a slice always returns a fresh list.

old_list = ["apple", "banana", "cherry"]
new_list = old_list[:]
new_list.append("mango")
print("old_list:", old_list)
print("new_list:", new_list)

Output

old_list: ['apple', 'banana', 'cherry']
new_list: ['apple', 'banana', 'cherry', 'mango']

The list() function works too. You hand it the old list and it builds a new list from those items.

old_list = ["apple", "banana", "cherry"]
new_list = list(old_list)
new_list.append("mango")
print("old_list:", old_list)
print("new_list:", new_list)

Output

old_list: ['apple', 'banana', 'cherry']
new_list: ['apple', 'banana', 'cherry', 'mango']

All of them gave the same safe result. Here they are side by side so you can keep them straight.

Way to copy What it looks like Best when
.copy() method new = old.copy() You want the clearest, most readable line
Slice new = old[:] You like short code and know the slice trick
list() function new = list(old) You are copying from any sequence, not just a list

🧩 The Catch With Lists Inside Lists

The ways above make what we call a shallow copy. A shallow copy builds a new outer list, but the items inside are still shared. For plain items like strings and numbers that is totally fine. You never change those in place anyway.

But what if your list holds other lists? Like a list of student scores, where each student has their own little list. Now the shallow copy gives you a new outer list, but the inner lists are still shared between the two. Change an inner list and both copies see it.

Let’s watch it happen. This code copies a list of lists with .copy(), then changes one inner list.

old_list = [["Alex", 90], ["Riya", 85]]
new_list = old_list.copy()
new_list[0].append("A grade")
print("old_list:", old_list)
print("new_list:", new_list)

Output

old_list: [['Alex', 90, 'A grade'], ['Riya', 85]]
new_list: [['Alex', 90, 'A grade'], ['Riya', 85]]

There is the surprise again, one level deeper. We changed new_list[0], but old_list[0] changed too. The outer list was copied, but the inner ["Alex", 90] list was still shared.

When you need every level to be its own separate copy, you use deep copy. It lives in the copy module. It copies the outer list and every list inside it, all the way down.

import copy
old_list = [["Alex", 90], ["Riya", 85]]
new_list = copy.deepcopy(old_list)
new_list[0].append("A grade")
print("old_list:", old_list)
print("new_list:", new_list)

Output

old_list: [['Alex', 90], ['Riya', 85]]
new_list: [['Alex', 90, 'A grade'], ['Riya', 85]]

Now old_list is fully safe. copy.deepcopy() made a new copy of the inner lists too, so the two are truly separate.

Tip

Reach for copy.deepcopy() only when your list actually holds other lists or dictionaries and you plan to change them. For a flat list of strings or numbers, a normal copy like .copy() is simpler and faster.

⚠️ Common Mistakes

A few traps come up again and again. Watch for these.

  • Thinking = copies a list. It does not. It only adds a second name for the same list.
# ❌ Avoid: this is not a copy, both names share one list
backup = scores
scores.append(100) # backup changes too
# ✅ Good: this makes a real, separate copy
backup = scores.copy()
scores.append(100) # backup stays the same
  • Using a shallow copy when your list holds other lists, then being surprised the inner lists are still shared.
# ❌ Avoid: inner lists are still shared
new_grid = old_grid.copy()
# ✅ Good: deep copy when there are lists inside lists
import copy
new_grid = copy.deepcopy(old_grid)
  • Reaching for deepcopy on a flat list out of fear. It works, but it is slower and not needed. A plain .copy() is enough for strings and numbers.

✅ Best Practices

  • Use .copy() as your default. It reads clearly and anyone can see what it does.
  • Save copy.deepcopy() for lists that hold other lists or dictionaries you intend to change.
  • When you are not sure if two names share one list, check with is. It tells you the truth.
  • Copy a list before a function changes it, so the caller’s original stays safe.

🧩 What You’ve Learned

  • new_list = old_list does not copy. It makes a second name for the same list. We call that aliasing.
  • ✅ Changing one aliased name changes the other, because there is really only one list.
  • .copy(), [:], and list() all build a real, separate copy of a flat list.
  • ✅ These are shallow copies, so lists inside the list are still shared.
  • copy.deepcopy() copies every level, so nested lists become truly separate too.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    After new_list = old_list, what is the relationship between the two names?

    Why: Assigning with = does not build a new list. Both names point at the same one, which is called aliasing.

  2. 2

    Which of these does NOT make a separate copy of a flat list?

    Why: The first three each build a new list, but new_list = old_list only adds a second name for the same list.

  3. 3

    You copy a list of lists with .copy() and change an inner list. What happens to the original's inner list?

    Why: .copy() is a shallow copy, so the inner lists are still shared between both copies.

  4. 4

    When should you use copy.deepcopy()?

    Why: Deep copy copies every level, which you need only when there are nested lists or dictionaries you will modify.

🚀 What’s Next?

You now know how to copy a list safely and avoid the aliasing trap. Next we leave lists behind and meet a close cousin that cannot be changed at all, which makes it perfect for data you want to keep fixed.

Introduction to Tuples

Share & Connect