14.4 Modifiers in Mapped Types: readonly and ? with + and -
Right, let’s talk about making your mapped types less… one-note. You’ve learned how to create a new type by iterating over another one, but so far, we’ve just been copying properties verbatim. That’s like having a remote control with only a power button—functional, but you’re missing all the good channels. The readonly and ? modifiers are your volume and channel buttons. And the real magic? You can add or remove these modifiers on the fly using + and -. This is where you stop just describing types and start sculpting them.
By default, when you map a property, its modifiers come along for the ride. If the original property was optional or readonly, the mapped property will be too. But we’re not default people. We’re engineers. We take control.
Controlling Optionality with ? and -?
Let’s say you have a configuration object that’s full of optional properties because, well, who has all the settings at startup? But for a internal validation function, you need everything to be required. You need to shake the user down for every possible value.
interface AppConfig {
hostname?: string;
port?: number;
retryAttempts?: number;
}
// This mapped type removes the optionality by appending -?
type RequiredConfig = {
[K in keyof AppConfig]-?: AppConfig[K];
};
// The result is:
// {
// hostname: string;
// port: number;
// retryAttempts: number;
// }
The -? modifier is your “make this required” tool. It strips the ? off the property. It’s brilliantly literal. The inverse operation is +? (or just ? since + is the default), which adds optionality. Why would you do that? Often when you’re creating a partial update object.
// This is so common, TypeScript provides it as a built-in: Partial<T>
type MakeEverythingOptional<T> = {
[K in keyof T]+?: T[K];
};
Toggling Mutability with readonly and -
The readonly modifier works exactly the same way. You can add it with +readonly (or just readonly) and strip it away with -readonly. This is your key to creating immutable versions of types for functional programming, or mutable versions for internal mutation.
Imagine you get a database row object that’s marked as readonly (a smart practice). But inside your ORM, you need to set a lastUpdated field. You need to temporarily revoke its readonly-ness.
interface DatabaseRow {
readonly id: string;
readonly name: string;
readonly createdAt: Date;
}
// Create a mutable version for our internal ORM mechanics
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
type MutableRow = Mutable<DatabaseRow>;
// Now, MutableRow has:
// {
// id: string;
// name: string;
// createdAt: Date;
// }
// No 'readonly' in sight. We can assign to these properties.
The Pitfall: Modifiers Are Additive (Until They’re Not)
Here’s the thing that trips everyone up, and it’s a classic TypeScript “gotcha”: these modifier operations happen after the mapping. You have to think about the order of operations.
You can’t just map over a type and decide to make some properties required and others readonly in a single pass based on the property name. The +, -, ?, and readonly are applied uniformly to every single property in the resulting mapped type. If you need fine-grained control, you need to use conditional types within the mapping or combine multiple mapped types. This is the point where you mutter, “Well, that’s a bit of a design flaw,” and then get on with your life because the workarounds aren’t that bad.
Why This Matters: The Big Picture
This isn’t just academic. This is the foundation for TypeScript’s most useful built-in utility types. Partial<T>, Required<T>, and Readonly<T> aren’t magic keywords—they’re just expertly applied mapped types with these exact modifiers.
// This is literally (almost) how Required is defined in the TypeScript lib.
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Mastering + and - with these modifiers transforms you from someone who uses types into someone who designs them. You stop asking “does a type exist for this?” and start saying “I’ll just make the type I need.” It’s the difference between ordering off a menu and cooking the meal yourself. And trust me, the chef’s table is a lot more fun.