Conditional Types

The Course
•
4 min read

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.

Intermediate warm-upTypeScript
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.

Predict, then runTypeScript
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.

Find and fix the type errorTypeScript
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

Exercise

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.

Your workspaceTypeScript
// 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
One possible solutionTypeScript
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 false

Trace 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.

Was this article helpful?

Your response is saved on this device.