A template literal type builds string types using backticks and substitutions, just as a JavaScript template builds text values. The example below constructs the property names onAdded and onRemoved from event keys. This describes allowed names; it does not allocate a handler object.
Start with names, then map keys
First form just the names: a template literal type substitutes each member of a string-literal union. The type alias below describes two strings; it does not create either callback. Capitalize is a built-in type helper turning the first character into uppercase. It is not a function you call on runtime text.
type Name = "added" | "removed";
type HookName = `on${Capitalize<Name>}`;
const first: HookName = "onAdded";
const second: HookName = "onRemoved";
console.log(first, second);Enable JavaScript to edit and type-check this example.
Check your prediction
onAdded onRemoved Now read as inside the mapped type as “use this new property name.” This is different from the value assertion value as SomeType: key remapping does not assert that an unchecked value is safe.
A mapped type can rename each key using as. In Hooks below, K extends string ensures the key can be passed to the built-in Capitalize string transformation. The false branch is never, which drops non-string keys from this derived type. Capitalize changes the first character’s type-level spelling, not a runtime name.
Read the mapping in steps: select a key, choose its new name, and preserve the old value relationship through T[K]. This combines conditional types, literal strings and indexed access; none of these syntax pieces should be mysterious now.
Keep remapping modest. A dozen layers of generated names can make errors harder to read. If runtime code needs those new names, construct the object explicitly or write and test a runtime transformation; a mapped type alone cannot produce it.
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 Hooks<T> = {
[K in keyof T as K extends string ? `on${Capitalize<K>}` : never]: (
value: T[K]
) => string;
};
type Events = {
added: string;
removed: number;
};
const hooks: Hooks<Events> = {
onAdded: title => title.toUpperCase(),
onRemoved: count => String(count)
};
console.log(hooks.onAdded("Types"));
console.log(hooks.onRemoved(2));Enable JavaScript to edit and type-check this example.
Check your prediction
TYPES
2 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 Labels<T> = {
[K in keyof T as K extends string ? `${K}Label` : never]: string;
};
const labels: Labels<{ added: number }> = { added: "Added" };
console.log(labels);Enable JavaScript to edit and type-check this example.
Check your diagnosis
The renamed property is addedLabel. Key remapping does not accept the original key as a substitute.
Build it yourself
Key Remapping: practise
Write Labels<T> remapping string keys to ${K}Label, with string values. Build labels for {added:number;removed:number} and print Added, Removed. Explain who constructs the runtime object.
// 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
The right-hand side is string here; the values do not need T[K].
Hint 2: a first step
Use a conditional key name ending in Label, then provide the actual object literal.
Show one solution
type Labels<T> = {
[K in keyof T as K extends string ? `${K}Label` : never]: string;
};
const labels: Labels<{
added: number;
removed: number;
}> = {
addedLabel: "Added",
removedLabel: "Removed"
};
console.log(labels.addedLabel + ", " + labels.removedLabel);Enable JavaScript to edit and type-check this example.
Added, RemovedTrace 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
Add a third event key. Observe which property becomes required. Compare an ordinary string template at runtime with the type-only template; write one sentence distinguishing them.
Completion is saved in this browser only. It does not measure mastery.