Question

How to Check a Variable Type in TypeScript with typeof

Question

Given a TypeScript variable that can hold either a number or a string:

let abc: number | string;

How can you determine whether its current value is a number or a string? Is there an equivalent to checking abc.type === "number"?

if (abc.type === "number") {
  // Do something
}

Short Answer

You will learn why TypeScript type annotations are not available at runtime, how to use JavaScript's typeof operator to inspect primitive values, and how TypeScript narrows a union type safely inside a conditional branch.

Concept

TypeScript lets you describe a value using types:

let abc: number | string;

This is a union type. It means abc may contain a number or a string.

However, number and string in this declaration are TypeScript compile-time types. They help the editor and compiler detect mistakes, but they are removed when TypeScript is compiled to JavaScript. Therefore, a normal primitive value does not have a .type property.

Use JavaScript's typeof operator to inspect primitive values at runtime:

if (typeof abc === "number") {
  // Here, TypeScript knows abc is a number.
}

This process is called type narrowing. Before the check, abc is number | string. In the if branch, TypeScript narrows it to number; in the else branch, it narrows it to string.

Type narrowing matters because number methods and string methods are different. You should check which kind of value you have before using operations that only one type supports.

Mental Model

Think of abc as a box that is allowed to hold one of two kinds of items: a number card or a text card.

The annotation number | string is a rule written on the box for TypeScript's compiler. It is not an object or label stored inside the box at runtime.

typeof abc is like looking at the item currently inside the box. Once you see it is a number card, TypeScript lets you use number-specific operations. If it is text, you can use string-specific operations.

Take Quiz

Syntax and Examples

The syntax for checking a primitive value is:

typeof value === "typeName"

For a number | string union:

function formatValue(value: number | string): string {
  if (typeof value === "number") {
    return `Doubled: ${value * 2}`;
  }

  return `Uppercase: ${value.toUpperCase()}`;
}

console.log(formatValue(21));       // Doubled: 42
console.log(formatValue("hello"));  // Uppercase: HELLO

Inside this condition:

if (typeof value === "number") {

TypeScript treats value as a number, so value * 2 is valid. After the early return, only the string case remains, so value.toUpperCase() is valid.

Common typeof result strings include:

typeof 42;          // "number"
typeof "hello";     // "string"
typeof true;        // "boolean"
typeof undefined;   // "undefined"
typeof 10n;         // "bigint"
typeof Symbol("x"); // "symbol"
typeof (() => {});  // "function"
typeof {};          // "object"

Step by Step Execution

Consider this function:

function describe(abc: number | string): string {
  if (typeof abc === "number") {
    return `The value is ${abc.toFixed(2)}`;
  }

  return `The text has ${abc.length} characters`;
}

console.log(describe(4));
console.log(describe("TypeScript"));

Execution for describe(4):

  1. abc receives 4.
  2. typeof abc evaluates to "number".
  3. The condition is true.
  4. TypeScript has narrowed abc to number in this block.
  5. abc.toFixed(2) returns "4.00".

Execution for describe("TypeScript"):

  1. abc receives the string "TypeScript".
  2. typeof abc evaluates to "string".
  3. The condition is false, so execution continues after the if block.
  4. TypeScript knows the remaining possible type is string.
  5. abc.length returns 10.

Real World Use Cases

Runtime type checks are useful when a value can arrive in more than one form:

  • API responses: An API field may return an ID as either 123 or "123".
  • Form inputs: Browser form values are usually strings, while application code may also receive numeric values programmatically.
  • Configuration: A setting might accept true, false, or a string such as "auto".
  • Reusable utilities: A formatter may accept a date timestamp as a number or a preformatted string.
  • Command-line arguments: Parsed options may need conversion or validation before use.

Example: normalize an ID before sending it to a database query.

function normalizeId(id: number | string): string {
  if (typeof id === "number") {
    return id.toString();
  }

  return id.trim();
}
Take Quiz

Real Codebase Usage

In production code, developers often pair narrowing with validation and early returns.

Guard clause

A guard clause handles an unsupported value immediately, keeping the main logic simple:

function calculateDiscount(value: number | string): number {
  if (typeof value !== "number") {
    throw new TypeError("Discount must be a number");
  }

  return value * 0.1;
}

Normalizing values at a boundary

Check values near the point where they enter your application, then return one predictable type:

function parsePage(value: number | string): number {
  if (typeof value === "number") {
    return value;
  }

  const page = Number(value);
  return Number.isInteger(page) && page > 0 ? page : 1;
}

Custom type guard for reusable checks

When a check is used in several places, create a function whose return type tells TypeScript what it proves:

function isNumber(value: unknown): value is number {
  return typeof value === "number";
}

function addTax(value: unknown): number {
  if (!isNumber(value)) {
    throw new TypeError("Expected a number");
  }

  return value * 1.2;
}

Use unknown for untrusted input. Unlike any, it requires you to validate the value before using it.

Common Mistakes

Using .type on a primitive

This does not check a TypeScript type:

let abc: number | string = 5;

if (abc.type === "number") {
  // Error: Property 'type' does not exist on type 'number'.
}

Use typeof abc === "number" instead.

Forgetting quotes around the type name

This is incorrect because number is not the string returned by typeof:

if (typeof abc === number) {
  // Incorrect
}

Write:

if (typeof abc === "number") {
  // Correct
}

Using typeof to distinguish arrays from objects

Arrays are objects at runtime:

typeof []; // "object"

Use Array.isArray(value) for arrays.

Expecting typeof null to return "null"

This is a long-standing JavaScript behavior:

typeof null; // "object"

Check it directly:

if (value === null) {
  // value is null
}

Declaring a variable without assigning it

With strictPropertyInitialization and strict checking, reading an unassigned local variable is an error:

let abc: number | string;
console.log(typeof abc); // Error: variable used before being assigned

Assign it first, receive it as a function parameter, or include undefined if that is genuinely possible:

let abc: number | string | undefined;

Comparisons

SituationRecommended checkWhy
number, string, boolean, undefined, bigint, symbol, or functiontypeof value === "number"typeof is designed for primitive runtime categories.
ArrayArray.isArray(value)typeof [] is "object".
Instance of a classvalue instanceof MyClassChecks whether the object comes from that class's prototype chain.
Object with a distinguishing field"kind" in value or value.kind === "..."Useful for object unions with a discriminant property.
nullvalue === nulltypeof null is incorrectly "object" in JavaScript.
Compile-time-only TypeScript type or interfaceValidate its shape at runtimeInterfaces and type aliases do not exist in emitted JavaScript.

For object unions, add your own property rather than expecting .type to appear automatically:

type Result =
  | { kind: "success"; value: number }
  | { kind: "error"; message: string };

function handle(result: Result): string {
  if (result.kind === "success") {
    return result.value.toFixed(2);
  }

  return result.message;
}
Take Quiz

Cheat Sheet

// Primitive type checks
if (typeof value === "number") {}
if (typeof value === "string") {}
if (typeof value === "boolean") {}
if (typeof value === "undefined") {}
if (typeof value === "function") {}

// A number-or-string union
function print(value: number | string) {
  if (typeof value === "number") {
    return value.toFixed(2); // value: number
  }

  return value.toUpperCase(); // value: string
}

// Special cases
Array.isArray(value);  // arrays
value === null;        // null
value instanceof Date; // class instances
  • TypeScript annotations are checked during compilation, not stored at runtime.
  • Use the exact lowercase strings returned by typeof, such as "number" and "string".
  • typeof null is "object"; compare with null directly.
  • typeof cannot verify a TypeScript interface or type alias at runtime.
  • Prefer unknown over any for data that must be validated.

FAQ

How do I check if a variable is a number in TypeScript?

Use typeof value === "number".

if (typeof value === "number") {
  console.log(value.toFixed(2));
}

Can I use variable.type to get a TypeScript type?

No. Primitive values do not automatically have a .type property, and TypeScript types are removed when code is compiled to JavaScript.

Does typeof narrow union types in TypeScript?

Yes. In a number | string union, checking typeof value === "number" narrows the value to number in that branch.

Why is typeof null equal to "object"?

It is a historical JavaScript quirk. Use value === null when you need to detect null.

How can I check whether a value is an array?

Use Array.isArray(value), not typeof value === "array".

if (Array.isArray(value)) {
  console.log(value.length);
}

Can typeof check a TypeScript interface?

No. Interfaces do not exist at runtime. Check required object properties, use a custom type guard, or use a validation library when validating external data.

Should I use any when a value may have several types?

Usually no. Use a union such as number | string when the possible types are known, or unknown for untrusted values that must be checked first.

Take Quiz

Mini Project

Description

Build a small formatter that accepts a product price supplied either as a numeric amount or as a text value. This resembles values received from an API, form field, or configuration file. The project demonstrates checking a union type and converting both forms into one consistent output.

Goal

Create a function that safely formats a number | string price as a two-decimal currency string.

Requirements

Accept a value typed as number | string.

Take Quiz

Keep learning

Related questions

View more