Skip to content

Mutable state

Mutable state is program information that can change during execution. Trace shared position lists, rebinding and the copy needed for a reliable snapshot.

Mutable state is information a program retains that can change as it runs. A trading system's current positions are useful state: fills must update them. The danger is losing track of which calculations see those updates. A cached position snapshot can silently become a view of today's positions if both names refer to the same mutable object.
In Python, lists and dictionaries permit changes to their contents. Mutation changes an existing object while preserving its identity; rebinding changes which object a name refers to. This distinction makes shared state much easier to reason about. Python's object model defines these object-level properties.

The snapshot that moved

Suppose a list holds share quantities for fictional assets A and B, always in that order. A report tries to save the list under another name:

positions = [2, 3] # shares of fictional assets A and B cached_positions = positions print(cached_positions is positions) # True cached_positions[0] = 5 print(positions, cached_positions) # [5, 3] [5, 3] positions = [8, 3] print(positions, cached_positions) # [8, 3] [5, 3] print(cached_positions is positions) # False
The first assignment creates an alias, another reference to the same list. The is test checks object identity. When the cached name changes A's quantity from 2 to 5 shares, the live name sees that change too. The cache did not preserve the earlier positions.
The later positions = [8, 3] behaves differently: it binds positions to a new list. The cached name still refers to the previous list, whose quantities are now [5, 3]. Python's assignment rules distinguish assigning to a name from assigning to an item's slot.
Two names share the fictional share-quantity list [2, 3]; changing its first item gives [5, 3] through both names. A separate shallow copy made before the update remains [2, 3].
Two names share the fictional share-quantity list [2, 3]; changing its first item gives [5, 3] through both names. A separate shallow copy made before the update remains [2, 3].
Original reference diagram for fictional assets A and B, in that order; quantities are shares. Assignment shares one list. A shallow copy of this flat integer list keeps the earlier quantities separate.

Copy at the boundary you need to preserve

For this flat list of integers, a shallow copy is enough to keep an earlier report's quantities:

positions = [2, 3] snapshot = positions.copy() positions[0] = 5 print(positions, snapshot) # [5, 3] [2, 3] print(snapshot is positions) # False

The lists are distinct objects. Replacing a slot in one list does not replace the corresponding slot in the other. The integer objects themselves are immutable; no recursive copying is needed for this example.

A shallow copy does have a boundary. Copying a dictionary creates a new outer dictionary while preserving references to its values. If a value is a list, that list can still be shared:

book = {"positions": [2, 3]} snapshot = book.copy() book["positions"][0] = 5 print(snapshot["positions"]) # [5, 3] print(snapshot is book, snapshot["positions"] is book["positions"]) # False True
Here, preserving only the outer dictionary does not preserve its nested position quantities. Choose a snapshot representation deliberately: copy the particular nested collections that may change, store immutable scalar records, or use a suitable deep copy. The copy documentation explains why deep copying can also copy data intended to remain shared. It is a tool, not a requirement for every snapshot. Immutability likewise needs to be considered at the level of the objects being protected.

Make the update contract visible

A function parameter can refer to the caller's list. Mutating that list can affect the caller even when the function returns nothing; merely rebinding the local parameter does not replace the caller's binding. Passing an input explicitly therefore makes the dependency visible, but does not by itself protect it from mutation.

For a position book, specify who owns updates and when a report captures its inputs. A function that updates the book should advertise that effect. A calculation that promises to preserve its inputs should avoid mutating shared objects. These choices support functional programming where a calculation fits that approach, while allowing stateful systems to process fills intentionally.
For a worked introduction, Collections covers shared objects and shallow copying. Its full lesson is free with a signed-in account.

Sources

Examples checked with Python 3.14.4. All position quantities are fictional.

Last updated
  • python
  • mutable state
  • aliasing
  • copying
  • reproducibility
← Back to the glossaryBack to top ↑