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.
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.
deepFreeze<T extends object>(obj: T): Readonly<T>
obj with Object.freezenull values and already-frozen objectsSample tests