How to Check the Type of a Variable in TypeScript

When you “check the type” of a variable in TypeScript, you’re really checking the runtime value and letting the compiler narrow the static type for you. In this tutorial, I’ll walk through the practical ways to do that using typeofinstanceof, and type guards, with examples you can drop into real projects.

How to check the type of a variable in TypeScript

In real applications, you rarely check a type just out of curiosity.
You usually do it because:

  • You need to call different logic depending on the value.
  • You’re working with union types and want IntelliSense to “know” the exact type.
  • You’re debugging something weird that came back from an API.

TypeScript doesn’t keep its types at runtime. At runtime you’re dealing with plain JavaScript values, and you use JavaScript tools like typeofinstanceof, and custom checks. TypeScript then uses those checks to narrow the type in your code so the editor and compiler can protect you from mistakes.

Runtime vs compile‑time types in TypeScript

Before we jump into code, it helps to have the right mental model.

  • TypeScript types are mostly erased at compile time.
  • At runtime, there is no string | number type anymore, only values.
  • When we “check the type,” we check the value and let TypeScript narrow the static type.

Take this function:

function logValue(value: string | number) {
if (typeof value === "string") {
// Here TypeScript treats value as string
console.log(value.toUpperCase());
} else {
// Here TypeScript treats value as number
console.log(value.toFixed(2));
}
}

Here’s what’s going on:

  • At runtime: typeof value === “string” is just a JavaScript expression.
  • In TypeScript: inside the if block, the type of value is narrowed to string.
    Inside the else, it’s narrowed to number.

If you hover over value in each block in VS Code, you’ll see the narrowed type change based on the check you wrote.

Use typeof to check primitive types

The simplest way to check the type of a variable in TypeScript is the typeof operator.
It returns a string describing the runtime type of the value.

Here’s a basic example:

const population = 331000000;          // number
const cityName = "New York"; // string
const isCapital = false; // boolean
const notSet = undefined; // undefined
const doSomething = () => {}; // function

console.log(typeof population); // "number"
console.log(typeof cityName); // "string"
console.log(typeof isCapital); // "boolean"
console.log(typeof notSet); // "undefined"
console.log(typeof doSomething); // "function"

You can see the output in the screenshot below.

Check the Type of a Variable in TypeScript

You’ll see those strings output in the console when you run this.

A few quick notes:

  • Use typeof for primitive types: stringnumberbooleanundefinedbigintsymbol, and function.
  • typeof is great when you have a union of primitive types and want to branch your logic.

A small gotcha: null and objects

This one surprises a lot of people:

console.log(typeof null);       // "object"
console.log(typeof { }); // "object"
console.log(typeof []); // "object"

So:

  • typeof null returns "object", even though null is not an object.
  • Arrays also return "object" with typeof.

Because of this:

  • Use Array.isArray(value) to check for arrays.
  • Use a null check plus typeof value === “object” to safely handle objects.

We’ll look at a full example of that later.

Use typeof in type guards (union types)

When you combine typeof with TypeScript’s union types, you get very readable code and strong type safety.

Imagine a function that accepts either a city name or a population count:

function printCityInfo(value: string | number): void {
if (typeof value === "string") {
console.log(`City name length: ${value.length}`);
} else {
console.log(`City population: ${value.toLocaleString("en-US")}`);
}
}

printCityInfo("Chicago"); // City name length: 7
printCityInfo(2716000); // City population: 2,716,000

You can see the output in the screenshot below.

How to Check the Type of a Variable in TypeScript

What’s happening:

  • The if (typeof value === “string”) is a type guard.
  • Inside that if, TypeScript treats value as a string, so you get value.length and string methods.
  • Inside the else, TypeScript knows it must be a number, so numeric methods and formatting work.

This pattern shows up all the time when you:

  • Work with optional configuration values.
  • Accept different forms of IDs (string ID vs numeric ID).
  • Handle API responses that might return different shapes.

Use instanceof for class instances

instanceof checks whether an object is an instance of a particular class at runtime.
It works with classes and constructor functions, but not with interfaces.

Here’s a simple example with vehicles:

class Vehicle {
constructor(public make: string, public model: string) {}
}

class Car extends Vehicle {
constructor(make: string, model: string, public year: number) {
super(make, model);
}
}

function printVehicleInfo(vehicle: Vehicle): void {
if (vehicle instanceof Car) {
console.log(`Car: ${vehicle.make} ${vehicle.model}, Year: ${vehicle.year}`);
} else {
console.log(`Vehicle: ${vehicle.make} ${vehicle.model}`);
}
}

const myCar = new Car("Tesla", "Model S", 2024);
printVehicleInfo(myCar);
// Car: Tesla Model S, Year: 2024

Key points:

  • At runtime, vehicle instanceof Car returns true if the object was created from Car (or a subclass).
  • In TypeScript, inside the if, the type of vehicle is narrowed from Vehicle to Car.
  • instanceof cannot be used with interfaces, because interfaces don’t exist at runtime.

For example, this won’t work:

interface Employee {
name: string;
}

// ❌ This is invalid:
function isEmployee(value: unknown): value is Employee {
// return value instanceof Employee; // Error: 'Employee' only refers to a type
return false;
}

If you need that behavior, you use a user‑defined type guard instead (next section).

User‑defined type guards (reusable checks)

Sometimes you want to centralize a type check in one place and reuse it across your codebase.
That’s where user‑defined type guards come in.

Let’s say you’re working with people who can be either employees or contractors:

interface Employee {
name: string;
employeeId: number;
}

interface Contractor {
name: string;
contractId: number;
}

function isEmployee(person: Employee | Contractor): person is Employee {
return (person as Employee).employeeId !== undefined;
}

function printPersonInfo(person: Employee | Contractor): void {
if (isEmployee(person)) {
console.log(`Employee: ${person.name}, ID: ${person.employeeId}`);
} else {
console.log(`Contractor: ${person.name}, Contract ID: ${person.contractId}`);
}
}

const john: Employee = { name: "John Miller", employeeId: 12345 };
const jane: Contractor = { name: "Jane Smith", contractId: 54321 };

printPersonInfo(john); // Employee: John Miller, ID: 12345
printPersonInfo(jane); // Contractor: Jane Smith, Contract ID: 54321

The key part is the return type of isEmployee:

function isEmployee(person: Employee | Contractor): person is Employee { ... }

That person is Employee tells TypeScript:

“If this function returns true, you can treat person as Employee in the calling code.”

Use this pattern when:

  • You need to check the same shape in multiple places.
  • You’re validating Typescript data from an API or form and want the compiler to understand your checks.
  • You’re wrapping validation libraries and want strong typing in your code that consumes them.

Check arrays and objects

Now let’s look at a common real‑world scenario: you get a value from an API, but you’re not quite sure what shape it has.

Here’s a helper that takes unknown and logs a message based on its runtime type:

function logValueDetails(value: unknown) {
if (Array.isArray(value)) {
console.log("Array with length:", value.length);
} else if (value !== null && typeof value === "object") {
console.log("Object with keys:", Object.keys(value));
} else {
console.log("Primitive value:", value);
}
}

logValueDetails(["Seattle", "Portland"]); // Array with length: 2
logValueDetails({ city: "Austin", state: "TX" }); // Object with keys: [ 'city', 'state' ]
logValueDetails(null); // Primitive value: null
logValueDetails("Denver"); // Primitive value: Denver

You can see the output in the screenshot below.

Check the Type of TypeScript Variable

What this does:

  • Array.isArray(value) is the correct way to detect arrays.
  • value !== null && typeof value === “object” safely checks for non‑null objects.
  • TypeScript’s unknown type forces you to narrow before using value, which is good for safety.

This is a useful pattern when dealing with JSON from APIs, configuration files, or anything coming from outside your code.

Common mistakes and simple best practices

A lot of bugs come from small misunderstandings.
Here are some practical do’s and don’ts I see in real projects.

Mistake 1: Treating typeof as a TypeScript feature

typeof is a JavaScript operator that works at runtime.
It does not know about TypeScript interfaces, type aliases, or generics.

Do this:

function handle(value: string | number) {
if (typeof value === "string") {
// Narrowed to string
}
}

Avoid trying to do this:

// ❌ This doesn't work:
if (typeof value === "Employee") { /* ... */ }

If you want to check for a particular shape, use a user‑defined type guard and property checks.

Mistake 2: Use typeof alone to handle arrays and objects

Because typeof [] and typeof {} both give "object"typeof alone isn’t enough.

Prefer:

  • Array.isArray(value) for arrays.
  • value !== null && typeof value === “object” for generic objects.

This becomes even more important when you’re dealing with data from Typescript’s REST APIs in a frontend app.

Mistake 3: Forget about null

This one bites people often.
They check typeof value === “object” and then immediately access a property, but value might be null.

Always include the null check:

if (value !== null && typeof value === "object") {
// Safe to treat as an object
}

A bug I’ve seen in real code: we assumed the API always sent an object, but when the backend had an error in Typescript, it sent null. Without the null check, the app crashed when trying to read a property. A simple value !== null would have prevented it.

Mistake 4: Scatter raw checks everywhere

In bigger codebases, checks like typeof x === “string” and Array.isArray(x) end up duplicated in many places.
That makes refactoring harder and often leads to inconsistent behavior.

When you notice a pattern:

  • Wrap it in a named function.
  • Turn it into a user‑defined type guard when it makes sense.

For example, instead of writing Array.isArray(user.roles) everywhere, you might have:

type Roles = string[] | "admin" | "guest";

function hasRoleArray(roles: Roles): roles is string[] {
return Array.isArray(roles);
}

Then you use hasRoleArray() throughout your code, and your intent is much clearer.

A small practice exercise

If you want to quickly solidify this, try this exercise in your editor or TypeScript playground:

Write a function that accepts:

type Input = string | number | string[];

Then:

  • If it’s a string, log its length.
  • If it’s a number, log it with two decimal places.
  • If it’s a string[], log how many items are in the array.

Hints:

  • Use typeof and Array.isArray.
  • Let TypeScript guide you with IntelliSense as you narrow the type.

This small exercise mirrors what you’ll do all the time in real-world TypeScript apps.

Summary and next steps

When you check the type of a variable in TypeScript, you’re really doing two things at once:

  • Using JavaScript mechanisms (typeofinstanceofArray.isArray, and property checks) to inspect the runtime value.
  • Letting TypeScript narrow the static type so you get safer, more expressive code.

Quick recap:

  • Use typeof for primitives like stringnumber, and boolean.
  • Use instanceof for class instances, not interfaces.
  • Use user‑defined type guards when you want reusable, readable checks that TypeScript understands.
  • Use Array.isArray and value !== null && typeof value === “object” when working with arrays and objects.
  • Watch out for null, and don’t rely on typeof alone for complex types.

If you build these patterns into your everyday coding habits, your TypeScript code will be easier to reason about and much harder to break accidentally.

You may also read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.