TryBugHunt

Shared references: when changing a copy changes the original

Assignment does not copy, a shallow copy still shares its contents, and mutating methods return None.

32 free challenges24 Python8 JavaScript

What are mutation and copying bugs?

Two names refer to the same underlying object, so a change through one is visible through the other. Or a method mutates in place and returns nothing, and the nothing gets assigned.

Why it happens

Assignment binds a name; it never copies. A shallow copy duplicates the outer container while its elements still point at the originals, so mutating a nested value is visible through both. Python evaluates default arguments once at definition, so a mutable default is shared by every call. And methods that mutate in place return None by convention, which is easy to assign by accident.

How to recognise it

  • A function that should be read-only changes its argument.
  • Two fresh objects share state.
  • A variable becomes None after a sort or reverse.
  • A list changes length while you iterate it, and elements get skipped.

Error guides caused by this pattern

All error guides →

In-depth guides for common exceptions and runtime errors triggered by mutation and copying bugs, with working fixes and real-world examples:

How to fix it

Copy explicitly, and match the copy's depth to the depth at which you mutate — a shallow copy protects only the top level. Use None as a default and build the mutable value inside the function. Remember which methods return a new value and which return None: that distinction marks the ones that mutate.

Breaks vs Works Example

Breaks

# Python: mutable default argument shared across all invocations
def add_tag(tag, tags=[]):
    tags.append(tag)
    return tags

Works

# Python: use None as sentinel default and initialize inside function
def add_tag(tag, tags=None):
    if tags is None:
        tags = []
    tags.append(tag)
    return tags

💡 Python default parameters are evaluated once when the function is defined, sharing state between calls.

Practise mutation and copying bugs

Working code with one bug in it. Find it, fix it in the browser, and see the explanation. No account needed.

pythonEasy
Receipt is empty after the cart is cleared

checkout(cart) saves the items as a receipt, empties the cart, and returns the receipt. Example: checkout(["pen", "ink"]) → ["pen", "ink"].

Other

pythonEasy
Shout function returns lowercase text

shout(text) returns the text in capital letters. Example: shout("hello") → "HELLO".

Other

pythonEasy
Squaring 3 gives 1

square(n) returns n squared. Example: square(3) → 9.

Other

pythonEasy
Tags after a comma start with a space

parse_tags(text) turns comma-separated text into tags. Example: parse_tags("python, debugging") → ["python", "debugging"].

Other

pythonEasy
Sorted name list comes back as None

sorted_names(names) returns the names in A to Z order. Example: sorted_names(["b", "a"]) → ["a", "b"].

Other

pythonEasy
try/except still crashes on 0 / 0

safe_ratio(done, total) returns done / total, or 0 when total is 0. Example: safe_ratio(1, 4) → 0.25, safe_ratio(0, 0) → 0.

Other

pythonEasy
Removing duplicate tracks changes the order

unique_tracks(track_ids) removes repeated tracks and keeps the original order. Example: unique_tracks([30, 10, 30, 20]) → [30, 10, 20].

Other

javascriptEasy
Shout button doesn't shout

shout(text) returns the text in capitals with an exclamation mark. Example: shout("hi") → "HI!".

Other

pythonEasy
Prices show $2.5 instead of $2.50

price_label(amount) shows a price with exactly two decimal places. Example: price_label(2.5) → "$2.50", price_label(3) → "$3.00".

Other

pythonEasy
Adding a tag makes the whole list disappear

add_tag(tags, new_tag) adds a tag to the list and returns the list. Example: add_tag(["python"], "debugging") → ["python", "debugging"].

Other

pythonEasy
Timer shows 2:5 instead of 2:05

format_duration(seconds) shows a length as minutes:seconds, with two digits for seconds. Example: format_duration(125) → "2:05".

Other

pythonEasy
Adding two items puts a list inside the list

add_items(cart, new_items) adds every item in new_items to the cart. Example: add_items(["pen"], ["ink", "pad"]) → ["pen", "ink", "pad"].

Other

pythonEasy
Greeting prints but the page shows nothing

make_greeting(name) returns a greeting for the welcome page. Example: make_greeting("Asha") → "Hello, Asha!".

Other

pythonEasy
Pass list crashes when looping over scores

passed_students(scores) returns the names of students with 40 or more, from a dict like {"asha": 72, "ravi": 35}. Example: passed_students({"asha": 72, "ravi": 35}) → ["asha"].

Other

pythonEasy
Name splitter crashes on a middle name

first_and_last(full_name) returns the first and last name. Example: first_and_last("Mary Ann Smith") → ["Mary", "Smith"].

Other

pythonHard
Removing even numbers leaves one behind

remove_evens(nums) returns the list without even numbers. Example: remove_evens([2, 4, 6, 1]) → [1].

Other

pythonHard
Editing a copied grid changes the original

original_after_edit(grid) copies a grid, changes the copy, and returns the original's first cell, which should not change. Example: original_after_edit([[1, 2], [3, 4]]) → 1.

Other

javascriptHard
AI Attempt: The English List

An AI assistant wrote formatNames(names), which turns a list into English, like ["A", "B", "C"] → "A, B and C". It must not change the list it is given. test_format checks that. Example: test_format(["Alice", "Bob", "Charlie"]) → "Alice, Bob and Charlie".

Other

javascriptHard
Adding 1 and 2 gives "1.002.00"

addRounded(a, b) adds two numbers and returns the sum rounded to 2 decimal places, as a number. Example: addRounded(1, 2) → 3.

Other

pythonHard
Changing the copy also changes the original

with_theme(settings, theme) changes the theme in a copy and keeps the original as it was. It returns "new theme|original theme" so you can see both. Example: with_theme({"display": {"theme": "light"}}, "dark") → "dark|light".

Other

pythonMedium
Merging settings changes the defaults

merge_settings(defaults, overrides) returns a new dict with the overrides on top, and leaves defaults unchanged so it can be reused. check_merge only reports [merged mode, merged size, defaults mode, defaults size]. Fix merge_settings. Example: check_merge({"mode": "light"}, {"size": "big"}) → ["light", 2, "light", 1].

Other

pythonMedium
2.5 rounds down to 2 instead of up to 3

round_mark(mark) rounds an exam mark the school way: .5 always goes up. Example: round_mark(2.5) → 3, round_mark(2.4) → 2.

Other

javascriptMedium
Every team ends up with every player

splitTeams(players) puts each player in team 0 or team 1. Example: splitTeams([{ name: "a", team: 0 }, { name: "b", team: 1 }]) → [["a"], ["b"]].

Other

pythonMedium
Booking one seat books the whole column

book_seat(rows, cols, row, col) makes an empty seating grid and books one seat. Example: book_seat(2, 2, 0, 0) → [[1, 0], [0, 0]].

Other

pythonMedium
Factorial crashes with RecursionError

factorial(n) returns n × (n - 1) × … × 1. Example: factorial(5) → 120. factorial(0) and factorial(1) are both 1.

Other

pythonMedium
Adding up a dict's values crashes

total_values(data) adds up the values in a dictionary. Example: total_values({"a": 1, "b": 2}) → 3.

Other

javascriptMedium
0.1 + 0.2 gives 0.30000000000000004

addPrices(a, b) adds two prices and returns a result correct to the cent. Example: addPrices(0.1, 0.2) → 0.3.

Other

pythonMedium
Removing https:// eats the start of shop.com

domain_of(url) removes "https://" from the front of a web address. Example: domain_of("https://shop.com") → "shop.com".

Other

javascriptMedium
Flattening stops after one level

flattenDeep(arr) removes all nesting, however deep. Example: flattenDeep([1, [2, [3, [4]]]]) → [1, 2, 3, 4].

Other

javascriptMedium
Removing dashes removes only the first one

stripDashes(text) removes every dash. Example: stripDashes("a-b-c") → "abc".

Other

pythonMedium
Headline formatter writes Don'T

headline(text) makes the first letter of each word a capital. Example: headline("don't stop") → "Don't Stop".

Other

javascriptMedium
Status helper returns undefined

getStatus(code) wraps an HTTP status code in an object. Example: getStatus(200) → { ok: true }.

Other

Stuck on your own code?

Paste it into the visualizer and watch it run line by line, with every variable at every step. Free, and it runs in your browser.

Other bug patterns