Intermediate introduced a readonly mapped type. Now read the modifiers explicitly: -readonly removes a readonly permission restriction, and -? removes an optional-property marker. Neither expression creates or modifies a JavaScript object.
For a configuration type with optional fields, RequiredFields<T> can require callers to supply all fields. The runtime implementation still needs defaults or validation if data arrives incomplete. A declaration cannot fill missing values in JSON.
Do not confuse making a writable view with obtaining independent ownership. Two variables can still refer to the same object. Use a new object when a transformation promises a copy. Prefer built-in Required, Readonly and Partial in ordinary application code; these small definitions teach you to read the underlying syntax.
Keep transformations shallow. If a field contains a nested record, removing readonly at the outer level does not rewrite that nested record’s type. Test the exact caller behavior your API needs instead of assuming every modifier propagates recursively.
Predict, check, and run
Write the output before running. Use Check types first, then Check & run. Types disappear at runtime: explain both what the compiler checks and what the code actually does. Each editor is an independent ES module; await asynchronous work explicitly.
type RequiredFields<T> = { [K in keyof T]-?: T[K] };
type Writable<T> = { -readonly [K in keyof T]: T[K] };
type Options = {
readonly title?: string;
readonly limit?: number;
};
const options: Writable<RequiredFields<Options>> = { title: "Types", limit: 2 };
options.limit = 3;
console.log(options.title, options.limit);Enable JavaScript to edit and type-check this example.
Check your prediction
Types 3 Read the compiler error
Predict the rejected relationship before checking. Read the real diagnostic and its source location; fix the contract or the value instead of hiding it with an assertion.
type Complete<T> = { [K in keyof T]-?: T[K] };
const settings: Complete<{
label?: string;
enabled?: boolean;
}> = {
label: "Types"
};
console.log(settings);Enable JavaScript to edit and type-check this example.
Check your diagnosis
Removing ? requires enabled to be present; no runtime default is inserted.
Permissions and required fields change independently
Removing optional markers changes what a caller must supply; removing readonly changes which assignments are allowed through a view. Neither fills missing runtime data or allocates another record. Compose those transformations only when both changes fit the public API.
An explicit undefined union is another choice. Required does not mean nonblank or positive, and writable does not mean independently owned. Test the exact caller operation you want to accept or reject rather than treating these modifiers as a general validation tool.
type Complete<T> = { [K in keyof T]-?: T[K] };
type Draft = {
enabled?: boolean;
minutes?: number;
};
const value: Complete<Draft> = { enabled: false, minutes: 0 };
console.log(value.enabled, value.minutes);Enable JavaScript to edit and type-check this example.
Check your prediction
false 0 Completeness requires both fields, while false and zero remain legitimate values. Optional markers alone said nothing about their domain meaning.
type Writable<T> = { -readonly [K in keyof T]: T[K] };
const source: { readonly minutes: number } = { minutes: 20 };
const view: Writable<typeof source> = source;
view.minutes = 30;
console.log(source.minutes);
console.log(view === source);Enable JavaScript to edit and type-check this example.
Check your prediction
30
true The writable view shares the original record. Removing an assignment restriction did not create a copy; a library must avoid advertising this as an isolated mutable draft.
Try the rule on new inputs
Compare Required, Readonly and a new spread record. Name which operation changes checking permissions, which requires fields and which creates new runtime identity. Test a nested field separately.
Build it yourself
Mapped Modifiers: practise
Create Complete<T> removing optional markers and ReadonlyFields<T> adding readonly. Make a complete readonly settings record with label Types and enabled true. Print Types true; verify deleting enabled is a type error.
// Start from the stated contract. Add a valid call and an edge case.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
Compose two small type transformations rather than pretending one clones data.
Hint 2: a first step
The optional modifier belongs after the closing key bracket; readonly belongs before it.
Show one solution
type Complete<T> = { [K in keyof T]-?: T[K] };
type ReadonlyFields<T> = { readonly [K in keyof T]: T[K] };
type Draft = {
label?: string;
enabled?: boolean;
};
const settings: ReadonlyFields<Complete<Draft>> = {
label: "Types",
enabled: true
};
console.log(settings.label, settings.enabled);Enable JavaScript to edit and type-check this example.
Types trueTrace the implementation against the task before comparing styles. A passing example supports that case; use the changes below to challenge the wider contract.
Change it and test it
Try a missing enabled property and a nested mutable array. Explain the separate effects of completeness, readonly and copying. Compare with Readonly<Required<Draft>>.
Completion is saved in this browser only. It does not measure mastery.