This course builds on TypeScript intermediate. You should be able to explain generic callbacks, mapped types, Result unions, unknown decoders and compile-time tests. Revisit generic callbacks, array decoders and contract tests if needed. JavaScript advanced is useful extra practice, but not required. No framework or decorator knowledge is assumed.
Before learning a new tool, recall the boundary: a Result success must be narrowed before reading value, and an unknown input needs runtime validation. TypeScript cannot make a server’s data true. This week we build a small event gateway whose public types preserve relationships and whose runtime enforces a separate contract.
Check the bridge from intermediate
Before adding syntax, explain this familiar decoder: why does its argument start as unknown, why must the caller check ok, and which runtime rule rejects the second input? Try it before opening the prediction. If you cannot explain those steps, revisit Result types and decoder contracts.
type Result<T> =
| {
ok: true;
value: T;
}
| {
ok: false;
message: string;
};
type Decoder<T> = (input: unknown) => Result<T>;
const decodeTitle: Decoder<string> = input => {
if (typeof input !== "string" || input.trim() === "") {
return { ok: false, message: "Expected title" };
}
return { ok: true, value: input.trim() };
};
const title = decodeTitle(" Types ");
console.log(title.ok ? title.value : title.message);
console.log(decodeTitle(25).ok);Enable JavaScript to edit and type-check this example.
Check your prediction
Types
false A type-level decision
A conditional type has the shape T extends U ? A : B. Read it as “if T is assignable to U, choose A; otherwise choose B.” This extends is a type relationship, not class inheritance. The conditional disappears from JavaScript; it cannot branch on an unknown runtime value.
In HasTitle below, the record shape is the test and true/false are literal types. Assigning a value to each result makes the invisible calculation observable to the checker. Predict whether each assignment compiles before running. Prefer a named domain type when there is no useful relationship to derive.
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 HasTitle<T> = T extends { title: string } ? true : false;
const book: HasTitle<{
title: string;
pages: number;
}> = true;
const count: HasTitle<number> = false;
console.log(book, count);Enable JavaScript to edit and type-check this example.
Check your prediction
true false 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 HasTitle<T> = T extends { title: string } ? true : false;
const answer: HasTitle<number> = true;
console.log(answer);Enable JavaScript to edit and type-check this example.
Check your diagnosis
The number type does not satisfy the record requirement, so the result is the literal false, not boolean.
Build it yourself
Conditional Types: practise
Write IsList<T>, returning true for readonly arrays and false for other inputs. Check a readonly string list and a number, then print true false. Explain why this is not an Array.isArray replacement.
// 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
Test assignability to readonly unknown[] so both mutable and readonly arrays qualify.
Hint 2: a first step
The two branches should be literal types true and false.
Show one solution
type IsList<T> = T extends readonly unknown[] ? true : false;
const list: IsList<readonly string[]> = true;
const scalar: IsList<number> = false;
console.log(list, scalar);Enable JavaScript to edit and type-check this example.
true falseTrace 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 assigning true to IsList<number>. Then write an actual runtime Array.isArray check on an unknown value and explain why the two operations solve different problems.
Completion is saved in this browser only. It does not measure mastery.