How to Make TypeScript Types Mutable (and Why You Might Need To)

How to Make TypeScript Types Mutable (and Why You Might Need To)
Photo by Tuân Nguyễn Minh / Unsplash

In TypeScript, immutability is a great feature that protects your data structures from unwanted changes. By using readonly properties, you can ensure that once an object is created, its shape or values can’t be modified accidentally. But sometimes, you do need to mutate the data — especially in scenarios like form editing, cloning objects, or local modifications before pushing changes to a database.

Let’s talk about how to convert readonly properties into mutable ones using a clean, reusable pattern in TypeScript.


🧱 The Problem: Readonly Types

Say you have this type:

type Example = {
  readonly a: string;
  readonly b: number;
};

If you try to mutate any of its properties:

const obj: Example = { a: "hello", b: 42 };
obj.a = "new value"; // ❌ Error!

You’ll get an error like:

Cannot assign to 'a' because it is a read-only property.

This is expected behavior — readonly is doing its job. But what if you need to re-use this type in a mutable context?


✅ The Solution: Mutable<T>

You can create a utility type that removes the readonly modifier from each property:

type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

Let’s break that down:

  • -readonly: removes the readonly modifier
  • [P in keyof T]: iterates over all properties in type T
  • T[P]: preserves the original type of each property

Now, using it like this:

type Result = Mutable<Example>;

Results in:

type Result = {
  a: string;
  b: number;
};

Now a and b are both mutable! 🎉


🧠 Additional Considerations

Here are a few other things you might want to know:

🔁 Deep vs Shallow Mutability

The Mutable<T> utility above is shallow — it only affects the top-level properties. If your type contains nested readonly objects, those will remain readonly.

For example:

type Nested = {
  readonly config: {
    readonly version: string;
  };
};

Using Mutable<Nested> will give you:

{
  config: {
    readonly version: string; // still readonly
  };
}

If you want deep mutability, you can use a recursive version:

type DeepMutable<T> = {
  -readonly [P in keyof T]: T[P] extends object
    ? DeepMutable<T[P]>
    : T[P];
};

Use with caution though — recursive types can get expensive and may lead to performance issues or type complexity in large codebases.


⚠️ When You Should NOT Use It

  • When working with data models meant to be immutable (e.g., Redux state, data fetched from APIs).
  • When mutability violates domain rules or business logic.
  • When you're passing objects between components that expect immutability for safety.

Mutability is powerful — but like any power, use it wisely.


💡 Use Cases Where This is Handy

  • Modifying deeply nested config objects before sending to backend.
  • Building forms or UI states based on a readonly schema.
  • Testing and mocking with controlled changes.
  • Converting third-party types (e.g., from @types packages) into editable forms.

🧪 Bonus: Using as Mutable<Type>

Sometimes, you just want a quick escape hatch. You can use TypeScript's type assertion if you're sure of what you're doing:

(obj as Mutable<Example>).a = "updated";

This is unsafe, so avoid it unless necessary, and always validate that mutation won't break something downstream.


🔚 Finally

Using Mutable<T> is a clean, reusable, and type-safe way to convert readonly types into mutable ones in TypeScript. It’s a powerful tool in the right contexts — especially when you're dealing with complex data transformations or need flexibility while still keeping type safety.

Always weigh the benefits of mutability against the risks of unintended side effects.

Support Us