Teaches when type assertions are safe versus dangerous in TypeScript...
as keyword or angle bracket syntax <Type>Type assertions are TypeScript compiler directives, not runtime operations.
const data = JSON.parse(json) as User;
This compiles fine but provides ZERO runtime safety. If JSON is malformed, your code crashes.
Rule: Type assertions are safe only when YOU control the data or have already validated it.
Question 1: Where does this data come from?
Question 2: Have you validated the data?
Question 3: Is this a TypeScript limitation?
❌ Asserting external API data
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json() as User;
return data;
}
Problem: If API returns different structure, runtime crash. TypeScript provides no protection.
✅ Validate instead
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data: unknown = await response.json();
if (!isUser(data)) {
throw new Error("Invalid user data from API");
}
return data;
}
❌ Asserting JSON.parse result
const config = JSON.parse(configString) as Config;
Problem: If JSON is malformed or wrong shape, crash at runtime.
✅ Validate with Zod
const data: unknown = JSON.parse(configString);
const config = ConfigSchema.parse(data);
❌ Asserting user input
function handleSubmit(formData: FormData) {
const user = {
name: formData.get("name"),
email: formData.get("email")
} as User;
saveUser(user);
}
Problem: FormData can contain anything. No validation.
✅ Validate form data
function handleSubmit(formData: FormData) {
const data = {
name: formData.get("name"),
email: formData.get("email")
};
const user = UserSchema.parse(data);
saveUser(user);
}
✅ Const assertions
const routes = [
{ path: "/", component: "Home" },
{ path: "/about", component: "About" }
] as const;
type Route = typeof routes[number];
Safe because: Data is hardcoded, not external.
✅ After validation
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data: unknown = await response.json();
const result = UserSchema.safeParse(data);
if (!result.success) {
throw new Error("Invalid user data");
}
return result.data as User;
}
Safe because: Data validated before assertion. (Though result.data already has correct type, so assertion is redundant.)
✅ Constructor results
class User {
constructor(
public id: string,
public name: string
) {}
}
const users = [
new User("1", "Alice"),
new User("2", "Bob")
] as User[];
Safe because: You control construction, types are guaranteed.
✅ Type narrowing limitations
interface Circle { kind: "circle"; radius: number; }
interface Square { kind: "square"; size: number; }
type Shape = Circle | Square;
function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
}
return shape.size ** 2;
}
Safe because: TypeScript narrows to Square after checking for circle. Use else to avoid assertion.
✅ Type widening prevention
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
} as const;
Safe because: Preventing literal types from widening to general types.
✅ Unknown to specific after validation
function processError(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;
return String(error);
}
Safe because: Type guards narrow before use without assertions.
❌ Double assertion to bypass safety
const value = "not a number" as unknown as number;
Problem: Intentionally bypassing type system. Defeats TypeScript's purpose.
✅ Fix the types properly
const value: unknown = "not a number";
if (typeof value === "number") {
console.log(value.toFixed(2));
}
❌ Non-null assertion on external data
const user = await fetchUser(id);
console.log(user!.name);
Problem: If fetchUser can return null, this crashes.
✅ Check explicitly
const user = await fetchUser(id);
if (user) {
console.log(user.name);
} else {
console.log("User not found");
}
✅ Non-null assertion after explicit check
const element = document.getElementById("root");
if (!element) {
throw new Error("Root element not found");
}
element.appendChild(child);
Safe because: Checked for null and threw. TypeScript narrows automatically, no assertion needed.
❌ Assertion to avoid validation
function getUser(data: unknown): User {
return data as User;
}
✅ Assertion function with validation
function assertIsUser(data: unknown): asserts data is User {
if (!isUser(data)) {
throw new Error("Invalid user data");
}
}
function getUser(data: unknown): User {
assertIsUser(data);
return data;
}
For related patterns:
asserts value is Type) over direct assertions!)SHOULD:
as const for literal type inferenceNEVER:
as unknown as Type)!) without prior checkType assertion is safe when ALL of these are true:
If ANY checkbox is false, use validation instead.
function parseUser(data: unknown): User {
const result = UserSchema.safeParse(data);
if (!result.success) {
throw new ValidationError("Invalid user", result.error);
}
return result.data;
}
const API_ENDPOINTS = {
users: "/api/users",
posts: "/api/posts",
comments: "/api/comments"
} as const;
type Endpoint = typeof API_ENDPOINTS[keyof typeof API_ENDPOINTS];
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
function handleShape(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
default:
assertNever(shape);
}
}
function isDefined<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
const values = [1, null, 2, undefined, 3];
const defined = values.filter(isDefined);
Find assertions: grep -rn " as " src/ and grep -rn "!" src/ | grep -v "!=="
Classify each: External data → add validation; After validation → verify; Const assertion → keep; Bypassing types → fix types
Replace pattern:
const data = JSON.parse(json) as User;
Becomes:
const data: unknown = JSON.parse(json);
const user = UserSchema.parse(data);
Enable strict mode: Set "strict": true and "noImplicitAny": true in tsconfig.json