Encoding the API's real shape
In lesson 15 the compiler showed two errors. Now we fix them — not by adding guards by hand, but by encoding the API's real shape into types, so those bugs become impossible to write.
The discriminated union — the heart of the lesson. APOD returns either an image or a video. We describe them as two separate types with a shared discriminant, media_type:
interface ApodImage { media_type: 'image'; hdurl?: string; /* ... */ }
interface ApodVideo { media_type: 'video'; /* NO hdurl */ }
type Apod = ApodImage | ApodVideo;
media_type: 'image' isn't string — it's the literal 'image'. So when you check media_type, the compiler narrows the type:
if (pic.media_type === 'image') {
pic.hdurl; // ✅ exists
} else {
pic.hdurl; // ❌ compile error — videos have no hdurl
}
Lesson 6's media_type bug — a video URL inside an <img> — is now unwritable. The compiler won't let you reach hdurl until you've checked the type.
Literal unions. The same idea for small value sets. Rating = 0 | 1 | 2 | 3 | 4 | 5 instead of number — rating = 47 becomes an error. The set of legal values IS the type.
Illegal states, made unrepresentable. In the vanilla code isLoading and error were separate variables — nothing stopped both being true at once, and the UI rendered that "impossible" state as nonsense. Modelled as a union, that combination becomes unconstructable.
This is one of the most powerful ideas in types: make illegal states unrepresentable. Not "we hope nobody creates a bad state", but "a bad state can't even be described".