Type Predicates
typeof, instanceof, and in are all built-in narrowing mechanisms. TypeScript recognizes them and narrows automatically. But sometimes you need to encapsulate a narrowing check in its own function — one that you call from multiple places. That requires a type predicate: a function whose return type is value is T.
The problem type predicates solve
Section titled “The problem type predicates solve”Suppose you extract the isHighScoreShape check from the previous lesson into a reusable function:
function isHighScoreShape(value: unknown): boolean { return ( typeof value === 'object' && value !== null && 'score' in value && 'total' in value && 'date' in value );}It returns boolean. TypeScript does not propagate the narrowing through it:
const parsed: unknown = JSON.parse(raw);
if (isHighScoreShape(parsed)) { console.log(parsed.score); // Error: 'parsed' is still 'unknown'}The check happens, but TypeScript does not know that a true return means parsed is a HighScore. The narrowing is lost at the function boundary.
The value is T return type
Section titled “The value is T return type”Replace boolean with value is T in the return type annotation:
function isHighScore(value: unknown): value is HighScore { return ( typeof value === 'object' && value !== null && 'score' in value && typeof (value as any).score === 'number' && 'total' in value && typeof (value as any).total === 'number' && 'date' in value && typeof (value as any).date === 'string' );}Now TypeScript treats isHighScore as a type guard. When it returns true, TypeScript narrows the argument to HighScore at the call site:
const parsed: unknown = JSON.parse(raw);
if (isHighScore(parsed)) { console.log(parsed.score); // ✓ — parsed is HighScore inside this branch console.log(parsed.total); // ✓}The value is HighScore annotation is a promise to TypeScript — you are asserting that a true return means the value satisfies HighScore. TypeScript trusts the annotation but cannot verify the logic inside the function body. Write the check carefully.
Updating loadHighScore
Section titled “Updating loadHighScore”With isHighScore available, loadHighScore in quiz.ts becomes genuinely safe:
static loadHighScore(): HighScore | null { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; try { const parsed: unknown = JSON.parse(raw); return isHighScore(parsed) ? parsed : null; } catch { return null; }}JSON.parse now returns unknown — the right type for data whose shape is not guaranteed. isHighScore validates the shape and narrows it. Only a confirmed HighScore is returned; anything else is discarded.
Type predicates in array methods
Section titled “Type predicates in array methods”Type predicates work with Array.filter to produce narrowed arrays:
function isTriviaQuestion(value: unknown): value is TriviaQuestion { return ( typeof value === 'object' && value !== null && 'question' in value && 'correctAnswer' in value && 'allAnswers' in value );}
const mixed: unknown[] = [...rawData];const questions: TriviaQuestion[] = mixed.filter(isTriviaQuestion);// ✓ — TypeScript knows the result is TriviaQuestion[]Without the type predicate, mixed.filter(isTriviaQuestion) would return unknown[] — the predicate’s value is TriviaQuestion return type is what tells TypeScript to narrow the filtered result.
The is keyword is a promise
Section titled “The is keyword is a promise”A type predicate tells TypeScript “trust me — when this returns true, the value is T.” TypeScript cannot verify the logic inside the function. A predicate that lies compiles fine but causes runtime errors:
function isHighScore(value: unknown): value is HighScore { return true; // always returns true — compiled, but dangerous}This accepts any value as HighScore. TypeScript trusts the annotation. Write predicates carefully — they are one of the few places in TypeScript where runtime and compile-time can genuinely diverge.
Exercise
Section titled “Exercise”In a scratch file predicates.ts (import your interfaces from types.ts):
- Write
function isHighScore(value: unknown): value is HighScoreusing the complete shape check —typeof value === 'object',value !== null,'score' in value,typeof (value as Record<string, unknown>).score === 'number', and equivalent checks fortotal(number) anddate(string). This is the version you will use inquiz.tsin Module 07. - Write
function isTriviaQuestion(value: unknown): value is TriviaQuestionwith checks forquestion,category,difficulty,correctAnswer, andallAnswers. - Create a
const sampleData: unknown[] = [{ score: 7, total: 10, date: 'Jan 1' }, { broken: true }]. Call.filter(isHighScore)on it and confirm the return type isHighScore[]. - In Module 07,
isHighScoremoves intoquiz.tsand is used byQuizEngine.loadHighScoreto validate data fromlocalStoragebefore returning it.
- A type predicate has the return type
value is T— when the function returnstrue, TypeScript narrows the argument toTat the call site. - Without
value is T, returningbooleanfrom a validation function loses the narrowing through the function boundary. - Type predicates work with
Array.filter— afilter(fn)call whose callback hasvalue is Tproduces a narrowed array type. value is Tis a promise to the compiler — TypeScript cannot verify the logic inside the predicate. Write it accurately.- Type predicates are the correct tool for validating
unknowndata from external sources before use.