TypeScript Casting
Type casting tells the TypeScript compiler to treat a value as a more specific type than it can infer on its own.
What Casting Actually Does
Casting, more precisely called a type assertion in TypeScript, does not convert a value at runtime the way Number("5") does. It simply tells the compiler "trust me, I know this value's type better than you do," which changes how the code type-checks without changing any generated JavaScript.
The 'as' Syntax
const input = document.getElementById("email") as HTMLInputElement;
input.value = "hello@example.com";
const raw: unknown = "42";
const value = raw as string;
console.log(value.toUpperCase());Angle-Bracket Syntax
TypeScript also supports an older angle-bracket syntax for assertions. It behaves identically to as, but it conflicts with JSX syntax, so it's rarely used in .tsx files and the as form has become the de facto standard.
Angle-Bracket Assertion
const raw: unknown = "hello";
const greeting = <string>raw;
console.log(greeting.length);
// Equivalent using 'as':
const sameValue = raw as string;Casting Through unknown
TypeScript will refuse an assertion if the two types don't overlap at all, since that's almost always a mistake. When you genuinely need to cross between two unrelated types, such as narrowing an API response, route the assertion through unknown first.
- Direct assertions only work between compatible types, like a broader type and a more specific subtype.
- Casting through 'as unknown as T' bypasses the compatibility check entirely — use it sparingly.
- Assertions never validate data at runtime; use a runtime check or a library like zod if you need real safety.
- Prefer type guards over assertions whenever you can write a simple runtime check instead.
Casting an API Response
interface User {
id: number;
name: string;
}
async function getUser(): Promise<User> {
const response = await fetch("/api/user/1");
const data = (await response.json()) as unknown as User;
return data;
}Assertions vs Type Guards
An assertion is a promise you make to the compiler; a type guard is proof you give the compiler. Whenever the shape of your data isn't guaranteed, a runtime check is the safer choice because it actually verifies the value before you use it.
Preferring a Type Guard
function isUser(value: unknown): value is { id: number; name: string } {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value
);
}
const data: unknown = { id: 1, name: "Sam" };
if (isUser(data)) {
console.log(data.name.toUpperCase());
}Exercise: TypeScript Casting
What is the standard syntax for casting a value with the `as` keyword?