Creating Immutable Objects Safely in TypeScript

Exploring the key differences Between as const and Object.freeze in TypeScript

Suyeon Kang
suyeonme
3 min readMar 24, 2024

--

Photo by Jr Korpa on Unsplash

When developing an application, maintaining immutability plays a crucial role in enhancing code predictability. Immutable objects remain unchanged, preventing unintended state mutations and increasing the reliability of the application.

I would make an object immutable when requiring a constant, indicating a fixed value. TypeScript offers various methods to achieve immutability. In this article, we will explore the key differences between as constand Object.freeze which are common ways to ensure object immutability in TypeScript.

as const

as const is a const assertion that converts variables to literal types. Literal types are the narrowest types. With const assertion, you can apply the same type inference rules to letvariables as you would with constvariables.

let apple = "Apple" as const; // Type is Apple
let apple = "Apple" // Type is string
const apple = "Apple"; // Type is Apple
const primary = 1; // Type is 1

Moreover, as const not only converts the variable to a literal type, but also makes it read-only, prohibiting changes to the variable.

// Object
const person = {
name: "Suyeon",
job: "Developer"
} as const;

person.job = "Artist"; // Cannot assign to 'job' because it is a read-only property.

// Array
const persons = ["Suyeon", "John", "Hanna"] as const;
persons[0] = "Beautiful" // Cannot assign to '0' because it is a read-only property.

as const only makes variable read-only at compile time.

Since as const is a TypeScript-specific feature that doesn't exist in vanilla JavaScript, it enforces read-only status at compile time. Consequently, when using as const, being a dynamically typed language, can still infer or change the type of variables at runtime.

const person = {
name: "Suyeon",
job: "Developer"
} as const;

// After compile to Javascript
const person = {
name: "Suyeon",
job: "Developer"
};

as const makes all contents of nested objects immutable.

as const extends its immutability to all contents of nested objects by fixing all properties as literal types, making them immutable.

const person = {
name: 'Suyeon',
info: {
job: 'Developer',
hobbies: ['Scuba Diving', 'Reading']
}
} as const;

person.info.job = 'Artist'; // Cannot assign to 'job' because it is a read-only property.

Object.freeze

Object.freeze freezes an object. Freezing an object prevents extensions and makes existing properties non-writable and non-configurable. — MDN

const person = {
name: "Suyeon",
job: "Developer"
};

Object.freeze(person);
person.job = "Artist";

console.log(person); // {name: 'Suyeon', job: 'Developer'}

Object.freeze makes variable read-only both at compile time and runtime.

Object.freeze ensures that a variable remains read-only both at compile time and runtime.

Object.freeze provides shallow immutability.

However, Object.freeze provides shallow immutability, meaning it doesn’t make the values of nested objects immutable. For making complete immutable object, libraries like immer or immutable.js. may be necessary.

Wait a moment. What is Object.seal?

Object.seal seals an object, preventing new properties from being added or removed, but allowing the values of existing properties to be modified.

const person = {
name: "Suyeon",
job: "Developer"
};

Object.seal(person);

delete job.person; // false
person.job = "Artist"; // {name: 'Suyeon', job: 'Artist'}

Conclusion

While as constand Object.freeze both seem to provide the same way to make an object immutable, there are subtle differences. Understanding their differences and limitations is valuable for making informed decisions in your codebase. Happy Coding!

--

--