unknown, Type Guards, and Safe Assertions
Handle data whose shape you cannot trust, and learn why `as` is the escape hatch of last resort.
What you'll learn
- Explain the difference between `any` and `unknown`
- Write a type predicate that narrows an unknown value
- Describe the risk a type assertion introduces
Prerequisites
Explanation
Data from outside your program — a parsed JSON body, a message from another window — has no type the compiler can trust. TypeScript gives you two ways to describe it, and they are not equivalent.
any versus unknown
any disables checking. Every operation is allowed, including ones that will crash:
const data: any = JSON.parse(text);
data.user.name.toUpperCase(); // compiles; may crash three ways
unknown is the honest version: it accepts any value but permits nothing until you prove what it is.
const data: unknown = JSON.parse(text);
data.user; // rejected — you have not established that data has a user
That rejection is the feature. unknown forces the check that any let you skip.
Type predicates
Ordinary narrowing (typeof, Array.isArray) works on unknown too. For object shapes you write a type guard — a function whose return type is value is T:
interface User { name: string }
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
typeof (value as { name?: unknown }).name === "string"
);
}
value is User is a type predicate. It tells the compiler: if this returns true, treat the argument as a User from here on.
if (isUser(data)) {
console.log(data.name.toUpperCase()); // data is User
}
The predicate is a promise you are making. TypeScript cannot verify that the body actually checks what the signature claims — a guard that returns true unconditionally would compile and would be a lie. Keep guards small and obviously correct.
Note the null check. typeof null === "object" in JavaScript, so omitting it is a classic bug.
Type assertions
value as User tells the compiler to stop objecting. It performs no runtime check:
const user = JSON.parse(text) as User;
console.log(user.name.toUpperCase()); // crashes if name is missing
The type error disappears; the bug does not. An assertion is appropriate when you genuinely know something the compiler cannot — and it should be rare, narrow, and commented.
The order of preference: narrow if you can, guard if you must, assert only when you truly know better.
Example
The same untrusted value handled safely with a guard, versus asserted away with no check at all.
interface User {
name: string;
age: number;
}
function isUser(value: unknown): value is User {
if (typeof value !== "object" || value === null) return false;
const candidate = value as { name?: unknown; age?: unknown };
return typeof candidate.name === "string" && typeof candidate.age === "number";
}
function describe(value: unknown): string {
if (isUser(value)) {
return value.name + " is " + value.age;
}
return "Not a user";
}
console.log(describe({ name: "Ada", age: 36 }));
console.log(describe({ name: "Ada" }));
console.log(describe("nonsense"));
console.log(describe(null));Try it yourself
Remove the `value === null` check, then call describe(null) — a reminder that typeof null is "object".
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Write `isNonEmptyString(value: unknown): value is string` returning true only for strings with at least one character. Remember it must be a type predicate, not just a boolean.
Checks: isNonEmptyString is defined · Accepts non-empty, rejects empty · plus 1 hidden check
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Define `interface Point { x: number; y: number }` and write `isPoint(value: unknown): value is Point` that safely verifies both fields are numbers. It must return false for null.
Checks: isPoint is defined · Accepts a valid point · Rejects partial objects, null, and non-objects
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Reaching for `any` when `unknown` is meant. `any` removes checking; `unknown` defers it until you have proved the shape.
- Forgetting that `typeof null === "object"`, so an object check without a null check passes for null.
- Using `as` to silence an error. An assertion changes what the compiler believes, never what the value actually is.
Knowledge check
Takeaway
`unknown` makes you prove a shape before using it; `as` merely silences the compiler and proves nothing.
Summary
`unknown` accepts anything and permits nothing until narrowed, unlike `any`, which disables checking. Type predicates (`value is T`) let a function narrow for callers, and assertions (`as`) should be rare because they perform no runtime check.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.