unknown → Safe Types: Runtime Validation Patterns
Stop lying with `as`. Learn production patterns to convert unknown data (fetch, localStorage, env) into safe TypeScript types with guards and assertions.
On this page
What you’ll learn
- Why
unknownis the correct type for external data - Type guards vs assertion functions (
asserts) - A production pattern for validating API responses and storage
30‑second interview answer
When data comes from outside TypeScript (network, storage, user input), it should start as unknown. Converting unknown to a safe type requires runtime validation. Type guards (x is T) and assertion functions (asserts x is T) let you validate once at the boundary and keep the rest of the codebase type-safe without as casts.
The core truth: TypeScript can’t validate at runtime
This compiles and can still crash:
type User = { id: string; name: string };
const user = (await fetch("/api/user").then(r => r.json())) as User;
// If API returns { id: 123 }, your code is broken but TS is silent.The fix is: treat external data as unknown.
Step 1: Start with unknown
const data: unknown = await fetch("/api/user").then(r => r.json());Step 2: Validate with a type guard
type User = { id: string; name: string };
function isRecord(x: unknown): x is Record<string, unknown> {
return typeof x === "object" && x !== null;
}
function isUser(x: unknown): x is User {
if (!isRecord(x)) return false;
return typeof x.id === "string" && typeof x.name === "string";
}
async function getUser(): Promise<User> {
const data: unknown = await fetch("/api/user").then(r => r.json());
if (!isUser(data)) throw new Error("Invalid API response: User");
return data;
}Now getUser() is safe.
Assertion functions (asserts) for better ergonomics
Sometimes you want validation but keep the value name.
function assertUser(x: unknown): asserts x is User {
if (!isUser(x)) throw new Error("Invalid User");
}
const data: unknown = JSON.parse(localStorage.getItem("user") ?? "null");
assertUser(data);
// data is now UserPattern: validate env vars once
type Env = {
SITE_URL: string;
MONGODB_URI: string;
};
function assertEnv(x: any): asserts x is Env {
if (typeof x.SITE_URL !== "string") throw new Error("Missing SITE_URL");
if (typeof x.MONGODB_URI !== "string") throw new Error("Missing MONGODB_URI");
}
const env = process.env;
assertEnv(env);
// env is EnvThis is clean, and it keeps unsafe process.env usage out of your app.
Common mistakes
- Using
as Tto silence errors instead of validating - Writing guards that only check a single field (false confidence)
- Skipping boundary validation, then chasing bugs deep in the app
Production rule of thumb
- External data starts as
unknown. - Validate at the boundary.
- Inside the app, keep types strong and casts rare.
Interview questions
-
Q: Why is
unknownbetter thanany?- A:
unknownforces validation before use;anydisables safety.
- A:
-
Q: What’s the difference between a type guard and an assertion function?
- A: Guards return boolean (
x is T); assertions throw if invalid and narrow the type (asserts x is T).
- A: Guards return boolean (
Quick recap
unknownis the correct starting point for untrusted data.- Use guards/assertions to validate.
- This pattern scales across fetch, storage, and env.
Looking for structured, offline interview prep?
Free online lessons • Portable Amazon Kindle / Paperback
Frontend Interview Guide (2025 Edition)
By Rajesh Dhiman
Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.
JavaScript Interview Guide
By Rajesh Dhiman
Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.
Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.
Written by Rajesh Dhiman
Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.