You copy an object, change the copy, and the original changes too. You pass a list into a function, the function tidies it up, and now your caller's list is different. These moments feel like the language is haunted, but they all come from one idea: some values are copied when you assign them, and some are shared by reference.
Once you can tell which is which, a whole category of bugs stops being mysterious and becomes predictable. It is one of those fundamentals that pays back every day, in every language you touch.
Primitives copy, objects share
Simple values — numbers, booleans, short strings in most languages — are copied when you assign or pass them. Change the copy and the original is untouched, because they were never linked. This is why passing a number into a function and incrementing it inside does not change the caller's number.
Objects, arrays and other compound structures usually behave differently: the variable holds a reference — a pointer to the one underlying thing. Assigning it to another variable copies the pointer, not the contents, so both names now refer to the same object. Mutating through one name is visible through the other, because there is only one object.
The functions-mutate-their-arguments trap
This is why a function that "just sorts" or "just cleans" a list can quietly rewrite data its caller still relies on. The function received a reference to the same array, and sorting in place changed the shared thing. The fix is to decide deliberately: either document that the function mutates, or make a copy at the top and work on that.
Defaulting to non-mutating functions — take input, return a new value, leave the argument alone — removes an enormous amount of action-at-a-distance from a codebase. When a function must mutate for performance, making that explicit in its name and docs turns a hidden hazard into a clear contract.
Shallow copies are only skin deep
The usual "copy this object" tools produce a shallow copy: a fresh top-level object whose nested objects are still shared references to the originals. Change a nested field and both copies see it. For flat data that is fine; for nested data it reintroduces the exact bug you were trying to avoid.
When you genuinely need independence all the way down, reach for a deep copy — but know it is more expensive and can choke on cycles. Most of the time the cleaner answer is to avoid mutation in the first place, so the question of copying rarely comes up. Structure your data flow so shared references are read, not written.