Lesson 2-3 introduced the declaration let direction: "left" | "right", and the symbol in the middle deserves a name.
The | symbol builds a union type, read as "or": the value may be any one of the listed types. In lesson 2-3 both sides happened to be literal string types, which made the union a fixed menu of two exact values.
This unit uses the same symbol with whole types, as in string | number. The meaning does not change, only the size of each alternative does.
One variable, several possible types
A union type says a value is one of a fixed set of types:
type Id = string | number; function formatId(id: Id): string { return "ID-" + id; }
Database ids, user input, and web API fields are often "string or number" in real systems, and this is how you say so honestly. Note the type alias naming the union, which lesson 3-3 said interfaces cannot do.
The rule that makes unions safe: you may only do things that are valid for every member. String concatenation works for both members here, so "ID-" + id compiles. But id.toUpperCase() would be an error, numbers have no such method. To use member-specific operations you must first narrow, which is the next lesson.
One function, two member types
type Id = string | number; function formatId(id: Id): string { return "ID-" + id; } console.log(formatId(42)); console.log(formatId("abc-7"));
Output
ID-42 ID-abc-7
Both member types pass through the same function without any branching, because string concatenation is meaningful for a number and for a string alike.
Adding console.log(formatId(true)) makes the compiler object, since boolean is not one of the union's members. The error reads: argument of type 'boolean' is not assignable to parameter of type 'Id'. A union is a closed list, not a vague gesture at flexibility, and that closedness is what makes it checkable.
Why a string method is rejected on a union
In function f(x: string | number), the call x.toUpperCase() does not compile, because toUpperCase is not valid when x is a number.
TypeScript allows only the operations valid for every member of a union. It reasons about the worst case: since x might be a number at runtime, a string-only method is rejected outright, even on the runs where the argument really is a string.
This can feel strict at first, but consider the alternative. Allowing the call would mean the compiler accepts code that crashes for half its legal inputs, which is exactly the situation the type system exists to eliminate.
The way forward is to prove which member you have, using a check such as typeof x === "string". That proof step is called narrowing, and it is the subject of the next lesson.