BugHunt

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.

12 free challenges8 Python4 JavaScript

What is mutation and copying bug?

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.

Errors and symptoms this causes

  • editing copy changes original
  • list.sort() returns None
  • shared state between instances
  • mutable default argument
  • removing items skips every other one

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.

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
Uppercasing a string does nothing

shout(text) should return the text in uppercase, so "hello" becomes "HELLO".

Other

pythonEasy
Sorting the names returns nothing

sorted_names(names) should return the names in alphabetical order, so sorted_names(["b","a"]) returns ["a","b"].

Other

pythonHard
Removing items skips every other one

remove_evens(nums) should return the list with all even numbers removed.

Other

pythonHard
Copying a grid still shares its rows

The function copies a grid, edits the copy, then reports the ORIGINAL's first cell — which should be unchanged. For [[1,2],[3,4]] it returns 1.

Other

javascriptHard
Rounded values get glued together

addRounded(a, b) should add two numbers and return the result rounded to two decimal places, as a number. addRounded(1, 2) is 3.

Other

pythonHard
Copying settings changes the original

with_theme(settings, theme) should return a copy with a new theme, leaving the original untouched. It returns "newtheme|originaltheme" so you can see both.

Other

pythonMedium
Merging settings mutates the defaults

merge_settings(defaults, overrides) should merge overrides on top of defaults without changing the defaults. It returns "mode|numberOfDefaultKeys" so you can see both.

Other

pythonMedium
Factorial that never stops recursing

factorial(n) should return n! (n factorial), e.g. factorial(5) -> 120.

Other

pythonMedium
Summing a dict adds up the keys

total_values(data) should add up a dictionary's values, so total_values({'a': 1, 'b': 2}) returns 3.

Other

javascriptMedium
Money totals gain a fraction of a cent

addPrices(a, b) should add two prices in dollars and return a value correct to the cent.

Other

javascriptMedium
Flatten only unwraps one level

flattenDeep(arr) should flatten a nested array completely, however many levels deep it goes.

Other

javascriptMedium
Only the first dash gets removed

stripDashes(text) should remove every dash, so stripDashes("a-b-c") returns "abc".

Other

Other bug patterns