Harden deep clone with cycles
Build deep clone with cycles. The interviewer expects a small, reusable utility with clear behavior under repeated calls and invalid inputs.
Answer Strategy
For deep clone with cycles, start by stating the public contract before writing code: argument shape, return shape, mutation rules, error behavior, and whether work is synchronous, timed, cached, or cancellable.
A senior solution uses boring names for hidden state. If the function stores a timer, cache entry, listener, or in-flight promise, say who owns that state and how it is cleaned up.
After the baseline passes, harden the edge cases: empty input, repeated calls, invalid values, thrown callbacks, stable ordering, and memory lifetime. The reference below is written to be narrated line by line.
Reference Implementation: Deep Clone With Cycles
A production clone utility must preserve object graphs without recursing forever on cyclic references.
function deepClone<T>(value: T, seen = new WeakMap<object, unknown>()): T {
if (value === null || typeof value !== 'object') return value;
if (seen.has(value as object)) {
return seen.get(value as object) as T;
}
if (value instanceof Date) {
return new Date(value.getTime()) as T;
}
if (Array.isArray(value)) {
const copy: unknown[] = [];
seen.set(value, copy);
value.forEach((item, index) => {
copy[index] = deepClone(item, seen);
});
return copy as T;
}
const copy: Record<string, unknown> = {};
seen.set(value as object, copy);
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
copy[key] = deepClone(item, seen);
}
return copy as T;
}Runnable Playground
Edit the implementation and run the tests directly in the browser. For system design questions, the playground focuses on the core state/data logic that the UI would call.
function deepClone<T>(value: T, seen = new WeakMap<object, unknown>()): T {
if (value === null || typeof value !== 'object') return value;
if (seen.has(value as object)) {
return seen.get(value as object) as T;
}
if (value instanceof Date) {
return new Date(value.getTime()) as T;
}
if (Array.isArray(value)) {
const copy: unknown[] = [];
seen.set(value, copy);
value.forEach((item, index) => {
copy[index] = deepClone(item, seen);
});
return copy as T;
}
const copy: Record<string, unknown> = {};
seen.set(value as object, copy);
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
copy[key] = deepClone(item, seen);
}
return copy as T;
}Testing Strategy
Convert the answer into observable behavior. In a mid-senior interview, say which behaviors are covered by unit tests, interaction tests, accessibility checks, and one browser smoke path.
test('deepClone preserves values and cycles without sharing objects', () => {
const input: any = { user: { name: 'Ada' }, dates: [new Date('2026-01-01')] };
input.self = input;
const copy = deepClone(input);
expect(copy).not.toBe(input);
expect(copy.user).not.toBe(input.user);
expect(copy.user.name).toBe('Ada');
expect(copy.dates[0]).not.toBe(input.dates[0]);
expect(copy.self).toBe(copy);
});Interviewer Signal
Tests whether you can turn a familiar utility into a precise contract instead of coding only the happy path.
Constraints
- Define the function signature before coding.
- Do not rely on global mutable state unless it is part of the returned closure.
- Explain time and space cost for the common path.
Model Answer Shape
- Write the smallest public contract first.
- Cover empty input, repeated calls, thrown errors, and cleanup behavior.
- Keep implementation readable enough to narrate under interview pressure.
Tradeoffs
- A compact implementation is attractive, but explicit state names are easier to debug live.
- Supporting every possible input can distract from the core contract; state the scope before coding.
Edge Cases
- No arguments or undefined callbacks.
- Synchronous throw inside the wrapped function.
- Repeated calls before the previous result settles.
Testing And Proof
- Happy path with representative inputs.
- Boundary input and repeated invocation.
- Cleanup or cancellation if timers or promises are involved.
Follow-Ups
- How would you expose cancellation?
- How would the API change for React usage?