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.
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 as a , so is valid. After the early return, only the case remains, so is valid.
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):
abcreceives4.typeof abcevaluates to"number".- The condition is true.
- TypeScript has narrowed
abctonumberin this block. abc.toFixed(2)returns .
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
123or"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();
}
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 > ? page : ;
}
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:
Comparisons
| Situation | Recommended check | Why |
|---|---|---|
number, string, boolean, undefined, bigint, symbol, or function | typeof value === "number" | typeof is designed for primitive runtime categories. |
| Array | Array.isArray(value) | typeof [] is "object". |
| Instance of a class | value instanceof MyClass | Checks whether the object comes from that class's prototype chain. |
| Object with a distinguishing field |
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.
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 , not .
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.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.