Note freeze() prevents all changes. seal() allows modifying existing properties but not adding/removing. Both are shallow -- nested objects remain mutable.
// readonly is compile-time onlyconst arr: readonly number[] = [1, 2, 3];
(arr asnumber[]).push(4); // No runtime protection
example
interface Config {
readonly apiKey: string;
readonly maxRetries: number;
}
const config: Config = { apiKey: "secret", maxRetries: 3 };
// TypeScript prevents this:// config.apiKey = "changed"; // Error// But at runtime, nothing stops this:
(config as any).apiKey = "changed"; // Works at runtime!
console.log(config.apiKey); // "changed"// For real immutability, use Object.freeze:const safeConfig = Object.freeze({ apiKey: "secret", maxRetries: 3 });
// safeConfig.apiKey = "x"; // Runtime TypeError + compile error
output
// readonly is erased at runtime — use Object.freeze for true immutability
Note readonly, Readonly<T>, and ReadonlyArray are all compile-time only. They are stripped during compilation and provide zero runtime enforcement. Code that bypasses the type system (any casts, JavaScript interop) can still mutate. For sensitive data, combine readonly with Object.freeze or structuredClone.