Snapshots & Ownership

The Course
•
4 min read

A library must define ownership. Copying an options object can stop later replacement of a top-level setting from changing the current run. A spread copy is shallow: nested arrays or objects still share references. Copy only the data you actually promise to isolate.

Object.freeze prevents immediate property changes at runtime, but is also shallow. It does not recursively freeze nested records. A frozen configuration is not automatically safe to share with arbitrary mutation-prone collaborators.

For the pool, the numeric limit is captured at invocation. Input item objects are passed by reference to the mapper, so a mapper can mutate them; the library does not promise deep immutability. The output array is newly allocated. A clear small contract is better than implying that every value has somehow become isolated.

The prediction first shows the limits of a spread copy. The exercise then uses deliberate projection: only recognized configuration fields enter the snapshot. Spreading every property from an external options object could keep unrelated mutable capabilities alive.

Predict, run, and explain

Write the output and trace the important state changes before checking it. Then run the program. Each editor is independent. Async examples end with main() so this editor waits for their work.

Predict, then run JavaScript
const options = { limit: 2, nested: { name: "shared" } };
const snapshot = { ...options };

options.limit = 4;
options.nested.name = "changed";
console.log(snapshot.limit);
console.log(snapshot.nested.name);
Check your prediction
2
changed

A snapshot needs an explicit boundary

Copying and freezing solve different problems. A copy can separate ownership; a freeze restricts direct property changes on one object. Both are shallow unless the implementation explicitly handles deeper data. Do not imply that passing a frozen outer object isolates a nested capability.

For options, project the supported scalar fields and validate their values before capturing them. For mapper items, document whether references are shared. Copying arbitrary unknown objects is not automatically safer than a clear small ownership contract.

A snapshot needs an explicit boundary: trace JavaScript
const nested = { label: "Old" };
const config = Object.freeze({ limit: 2, nested });

nested.label = "New";
console.log(Object.isFrozen(config));
console.log(config.nested.label);
Check your prediction
true
New

The outer object is frozen while nested remains mutable. A test checking only isFrozen would not establish a deep immutability promise.

A snapshot needs an explicit boundary: contrast JavaScript
const source = { limit: 2, capability: { token: "private" } };
const snapshot = Object.freeze({ limit: source.limit });

source.limit = 4;
console.log(snapshot.limit);
console.log(Object.keys(snapshot).join(","));
Check your prediction
2
limit

Projection both captures the scalar setting and drops the unrelated reference. This is more specific than spreading every property and assuming the resulting object is an appropriate public snapshot.

Try the rule on new inputs

List every reference entering and leaving the pool. For each one, decide who may mutate it. Test a nested input item separately from the captured numeric limit so the two ownership promises cannot be confused.

Your turn

Exercise

Snapshots & Ownership: practise

Write readOptions(options) returning a new frozen object containing only limit. Require an integer limit from 1 to 64. Show that changing the original limit afterward leaves the snapshot at 2 and that its keys contain only limit.

Your workspace JavaScript
// Validate first, project the recognized field, then freeze the new object.
Hint 1: the idea

Neither spreading nor freezing is a general deep-copy operation.

Hint 2: a first step

Construct { limit: options.limit } instead of spreading all input keys.

Show one solution
function readOptions(options) {
  if (
    !Number.isInteger(options.limit) ||
    options.limit < 1 ||
    options.limit > 64
  ) {
    throw new RangeError("Invalid limit");
  }

  return Object.freeze({ limit: options.limit });
}

const source = { limit: 2, extra: [] };
const snapshot = readOptions(source);

source.limit = 4;
console.log(snapshot.limit);
console.log(Object.keys(snapshot).join(","));
2
limit

Explain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?

Change it and test it

Assert the snapshot is frozen with Object.isFrozen. Test invalid limits and null input, then decide whether null should have an explicit error message. Explain which references a mapper is allowed to mutate under the pool’s documented API.

Was this article helpful?

Your response is saved on this device.