MediumJavaScriptTypeScript

deepFreeze<T> — Runtime Readonly

TypeScriptTypesObjects

Object.freeze only freezes the top level of an object. Nested objects remain mutable:

const config = Object.freeze({ db: { host: 'localhost' } });
config.db = {};            // ❌ frozen — throws in strict mode
config.db.host = 'other';  // ✅ nested object is NOT frozen — mutates silently!

deepFreeze fixes this by recursively freezing all nested objects.

TypeScript angle

The compile-time equivalent is the DeepReadonly<T> utility type:

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

// deepFreeze is the runtime enforcement of DeepReadonly:
function deepFreeze<T extends object>(obj: T): Readonly<T>

Note: TypeScript's built-in Readonly<T> is shallow (only marks top-level properties as readonly). DeepReadonly<T> is a custom recursive mapped type.

Implement

deepFreeze<T extends object>(obj: T): Readonly<T>

  • Freeze obj with Object.freeze
  • Recursively freeze all object-valued own properties
  • Skip null values and already-frozen objects
  • Return the same object (mutated in place, just frozen)

Sample tests

Test #1flat object is fully frozen
Input: [{"a":1}]
Output: {"value":{"a":1},"isFrozen":true,"isDeepFrozen":true}
Test #2nested object is also frozen
Input: [{"a":{"b":1}}]
Output: {"value":{"a":{"b":1}},"isFrozen":true,"isDeepFrozen":true}
Test #3deeply nested 3 levels
Input: [{"a":{"b":{"c":42}}}]
Output: {"value":{"a":{"b":{"c":42}}},"isFrozen":true,"isDeepFrozen":true}
Test #4empty object
Input: [{}]
Output: {"value":{},"isFrozen":true,"isDeepFrozen":true}