This TypeScript error occurs when trying to use the readonly modifier in a mapped type without properly using the keyof operator. Mapped types require keyof to iterate over object keys when applying modifiers like readonly. The fix involves correctly structuring mapped types with keyof.
The error "Cannot use 'readonly' modifier in mapped type without keyof" occurs in TypeScript when you attempt to create a mapped type that applies the readonly modifier without properly using the keyof type operator. Mapped types in TypeScript allow you to create new types by transforming properties of existing types. When you want to make all properties of an object type readonly, you need to use a mapped type that iterates over the keys of the original type using keyof. The error happens because TypeScript expects mapped types that apply property modifiers (like readonly or ? for optional) to explicitly iterate over keys using the keyof operator. This ensures type safety and clarity about which properties are being transformed.
The correct syntax for creating a readonly version of an object type uses keyof to iterate over the keys:
// ❌ Incorrect - causes the error
type ReadonlyObject = {
readonly [K in string]: any;
};
// ✅ Correct - uses keyof with a source type
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Example usage
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// Equivalent to: { readonly name: string; readonly age: number; }If you have code causing this error, update it to use the correct syntax:
// Before - causes error
type MyReadonlyType = {
readonly [K in 'prop1' | 'prop2']: string;
};
// After - fixed with proper mapped type
type Keys = 'prop1' | 'prop2';
type MyReadonlyType = {
readonly [K in Keys]: string;
};
// Or using a helper type
type MakeReadonly<T extends Record<string, any>> = {
readonly [K in keyof T]: T[K];
};TypeScript provides a built-in Readonly<T> utility type that handles this correctly:
interface Config {
apiUrl: string;
timeout: number;
retries: number;
}
// Use the built-in Readonly utility type
type ReadonlyConfig = Readonly<Config>;
// Equivalent to: {
// readonly apiUrl: string;
// readonly timeout: number;
// readonly retries: number;
// }
// You can also create partial readonly types
type PartiallyReadonly<T, K extends keyof T> = Readonly<Pick<T, K>> & Omit<T, K>;
type ConfigWithReadonlyApi = PartiallyReadonly<Config, 'apiUrl'>;
// apiUrl is readonly, timeout and retries are mutableFor advanced use cases, create custom mapped types with readonly:
// Deep readonly - makes nested objects readonly too
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
// Readonly except for specific keys
type ReadonlyExcept<T, K extends keyof T> = {
readonly [P in Exclude<keyof T, K>]: T[P];
} & {
[P in K]: T[P];
};
interface State {
count: number;
user: { name: string; email: string };
timestamp: Date;
}
type ReadonlyState = DeepReadonly<State>;
// All properties including nested 'user' object are readonly
type StateWithMutableCount = ReadonlyExcept<State, 'count'>;
// Only 'count' is mutable, others are readonlyVerify that your readonly types prevent mutation:
interface Product {
id: string;
name: string;
price: number;
details: {
category: string;
inStock: boolean;
};
}
type ReadonlyProduct = Readonly<Product>;
const product: ReadonlyProduct = {
id: '123',
name: 'Widget',
price: 29.99,
details: { category: 'Tools', inStock: true }
};
// These will cause TypeScript errors:
// product.name = 'New Name'; // Error: Cannot assign to 'name' because it is a read-only property
// product.details.category = 'New Category'; // Error with shallow Readonly
// product.details = { category: 'New', inStock: false }; // Error
// For deep readonly:
type DeepReadonlyProduct = DeepReadonly<Product>;
const deepProduct: DeepReadonlyProduct = { ...product };
// This will also error:
// deepProduct.details.category = 'New Category'; // Error with DeepReadonly## Understanding Mapped Type Modifiers
TypeScript mapped types support two kinds of modifiers:
1. Readonly modifier (readonly): Makes properties immutable
2. Optionality modifier (?): Makes properties optional
You can also remove modifiers using -readonly and -?:
// Remove readonly from a type
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
// Remove optional modifier
type Required<T> = {
[K in keyof T]-?: T[K];
};## Common Pitfalls
1. Missing keyof: Always use keyof T when applying modifiers in mapped types
2. Shallow vs Deep: Built-in Readonly<T> only makes top-level properties readonly
3. Performance: Complex mapped types with many transformations can slow down type checking
4. Circular references: Avoid creating mapped types that reference themselves
## Real-World Use Cases
- Immutable state in Redux or state management libraries
- Configuration objects that shouldn't be modified after initialization
- API response types where data should be treated as immutable
- Library APIs that return readonly data structures
Type parameter 'X' is not used in the function signature
How to fix "Type parameter not used in function signature" in TypeScript
Type parameter 'X' is defined but never used
How to fix "Type parameter is defined but never used" in TypeScript
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript