Base CS from zero
Variables and state: build a state container
Reading about aliasing is not the same as watching your own snapshot history silently rewrite itself because two names shared one object. Build a small state container, record snapshots of its state over time, then break it on purpose with an aliasing bug and fix it — proving each step by tracing what is in the cells.
Turn the unit’s memory model into working code: represent program state as named variables, mutate it through controlled assignments, capture honest snapshots, and demonstrate firsthand the difference between sharing a reference and copying a value.
Build a tiny in-memory 'game state' container in TypeScript with a snapshot-history feature, then deliberately introduce an aliasing bug that corrupts the history, diagnose it from first principles, and fix it with an explicit copy — documenting the cell-level cause of each behaviour.
- Running the buggy version prints a history where every snapshot is identical to the final state, and your write-up names the reference-sharing as the cause — not a logic error in applyMove.
- Running the fixed version prints a history of distinct snapshots, each matching the state at the moment it was captured, with the nested inventory also independent per snapshot.
- A short written explanation distinguishes, with reference to the actual code, where a value was copied vs where a reference was shared, and why const on the state did not prevent the mutation.
- The mutation log clearly shows state as the cumulative result of the individual mutations, not a single jump.
- Add an undo() that restores the previous snapshot. Verify it does NOT reintroduce aliasing — restoring must not make the live state share a reference with a stored history entry, or a later mutation will corrupt history again.
- Replace the manual { ...state } copies with structuredClone and explain how that changes the nested-inventory behaviour compared to a shallow spread.
- Add a freeze() path using Object.freeze on each snapshot and show that an accidental mutation of a stored snapshot now throws (or is silently ignored in non-strict mode) — making the immutability of history explicit rather than conventional.
- Write a 6-question self-quiz (with answers) that would catch a teammate who still believes 'let copy = original' duplicates an object.
This is the loop behind a huge class of real bugs: state is just named cells you mutate over time, and a ‘snapshot’ is only honest if it copies values rather than sharing references. Storing a reference into a history list aliases the live object, so every later mutation rewrites the past; copying the value at capture time fixes it, and a shallow copy still leaves nested objects shared. Doing it once on a toy container makes the production version — Redux reducers, audit logs, change tracking — obvious instead of mysterious.