Advertisement

One of the most useful distinctions a developer can internalise is between mutable and immutable data. A mutable value can be changed in place; an immutable one cannot — any "change" produces a new value and leaves the original untouched. Which kind you are holding determines whether modifying data here quietly alters it somewhere else, and that is the seed of a whole category of baffling bugs.

The confusion usually surfaces the first time a list, array or object appears to change on its own — because a second variable was pointing at the same underlying data all along.

Sharing versus copying

When you assign a mutable value to a second variable, many languages copy the reference, not the data: both variables now point at the same object. Mutate it through one and the other sees the change, because there was only ever one object. This is efficient and often intended, but it surprises people who imagined they had made an independent copy.

Immutable values sidestep this entirely. Since they cannot be changed in place, sharing a reference is completely safe — nobody can alter what everybody is looking at. Strings and numbers are immutable in many languages precisely so passing them around never causes spooky action at a distance.

Advertisement

Where the bugs come from

The classic bug: a function receives a list, modifies it for its own purposes, and the caller's list silently changes too, because both referenced the same array. Or a "default" value that is a mutable object gets modified and mysteriously carries state between calls. These are not exotic edge cases; they are among the most common real-world defects, and they all trace back to modifying shared mutable state.

The tell is a value that changed when the code you were looking at never touched it. Nine times out of ten, something else holds a reference to the same mutable object and changed it.

Working with the grain

A few habits defuse most of these bugs. When you need an independent copy, make one deliberately — and know whether your language's copy is shallow (top level only) or deep (all the way down), because a shallow copy of a nested structure still shares its inner objects. Prefer immutable data where a language offers it, and prefer functions that return new values over ones that mutate their arguments.

The broader principle behind modern "functional" style is exactly this: reduce shared mutable state, and a large class of concurrency and side-effect bugs simply cannot occur. You do not have to go fully immutable — but knowing, at every moment, whether you can change a value in place is one of the quiet marks of a careful developer.