cf.completefrontend

Write the codeEasy

Write deepFreeze(object) so that an object and everything reachable from it is frozen.

Before you start

What's a circular reference?

A circular reference is a chain of properties that leads from an object back to itself. It can be direct, as in obj.self = obj, or pass through other objects, as when a team points to its lead and the lead points back to the team. There's nothing wrong with such data, and you can use it normally. The trouble is code that follows every property into every nested object: it goes round the loop and never reaches the end.

const team = { name: "Frontend" };
const ada = { name: "Ada", team };
team.lead = ada; // team -> ada -> team -> ...
 
ada.team.lead.team.lead.name; // "Ada": you can go round as often as you like
JSON.stringify(team);         // throws a TypeError

Here's a walk that visits every nested object, starting from team:

StepThe walk is atIt followsWhich leads to
1teamteam.leadada
2adaada.teamteam, where it started
3teamteam.leadada again, and so on forever

JSON.stringify detects the loop and throws a TypeError, since the text would never end. A function you write that copies, compares, or freezes nested objects, calling itself for each one, doesn't stop on its own: it keeps calling itself until the call stack runs out and the engine throws. So code that walks nested objects has to notice when it comes back round to an object it has already reached.

Reaching an object twice doesn't always mean a loop, though. A shared reference is one object reached by two different paths, with no way back to where you started:

const office = { city: "London" };
const staff = { ada: { office }, grace: { office } };
 
staff.ada.office === staff.grace.office; // true: one object, two paths
JSON.stringify(staff);
// '{"ada":{"office":{"city":"London"}},"grace":{"office":{"city":"London"}}}'
 
const copy = structuredClone(staff); // the built-in deep copy
copy.ada.office === copy.grace.office;   // true: one new office, still shared
copy.ada.office === office;              // false

JSON.stringify writes a shared object out once per path, so parsing the text back gives two separate offices. structuredClone keeps the sharing, and it keeps cycles too: copying team gives a new team whose lead points back to the new team.

Cycles are common in real data. A DOM element's parentNode has the element among its children, tree nodes often link to their parent, a doubly linked list points both ways, and globalThis.globalThis is globalThis.

Object.freeze is shallow: the objects inside a frozen object can still change. deepFreeze freezes those too: nested objects, arrays, and functions, including the ones held in symbol-keyed and non-enumerable properties.

Handle cycles. An object whose top level is already frozen still gets everything inside it frozen.

Return the object you were given. Return primitives unchanged.

Write this
function deepFreeze(object) {
  // your code
}
 
// const config = deepFreeze({ api: { url: "/v1" }, flags: ["beta"] });
// config.api.url = "/v2";        // ignored (a TypeError in strict mode)
// Object.isFrozen(config.flags); // true

Solve it in the editor, with tests

The answer

Write it yourself first. Then compare it with a reference solution and the checks it passes.

Checking your sign-in…