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;
Note: Angle-bracket assertions cannot be used in .tsx files because the parser confuses <string> with a JSX element. Always prefer 'as' in React projects.

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.

ApproachRuntime check?Best for
Type assertion (as)NoYou are certain of the type from context, e.g. DOM queries
Angle-bracket (<T>)NoLegacy code, never in .tsx files
typeof / instanceof guardYesNarrowing unknown or union types safely
Casting through unknownNoBridging two genuinely unrelated types

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());
}
Note: A common beginner mistake is thinking 'as Type' validates or converts data. It does neither — it only affects compile-time type checking.

Exercise: TypeScript Casting

What is the standard syntax for casting a value with the `as` keyword?