typeof means two things in TypeScript. In an expression it is the JavaScript operator: it runs, returns a string like "string" or "number", and TypeScript narrows the variable when you compare that string. In a type position it is a type query: typeof config gives the static type of the variable config.
The last line shows the difference: typeof custom in a console.log is the runtime operator and prints object, while typeof defaults after type Options = is the compile-time query and never reaches the JavaScript.
The Two Meanings at a Glance
Runtime typeof (JavaScript) | Type query typeof (TypeScript) | |
|---|---|---|
| Where it appears | Any expression: if, return, console.log(...) | Type positions: after :, in type X = ..., inside <...> |
| Example | typeof x === "string" | let y: typeof x; |
| Evaluated | When the code runs | By the compiler, then erased |
| Result | One of eight strings | A type |
| Used for | Checking a value and narrowing its type | Deriving a type from a value |
TypeScript tells them apart by position, so the same keyword never means both at once.
typeof Results at Runtime
The runtime operator returns one of eight strings. Run this to see what common values give:
| Value | typeof result | Note |
|---|---|---|
"hi", template strings | "string" | |
42, 3.14, NaN, Infinity | "number" | NaN is a number; check it with Number.isNaN |
10n | "bigint" | |
true, false | "boolean" | |
undefined | "undefined" | |
Symbol("id") | "symbol" | |
| functions, arrow functions, classes | "function" | a class is a constructor function |
null | "object" | a historical bug in JavaScript |
{}, [], new Date(), new Map() | "object" | arrays are objects: use Array.isArray |
new String("x"), new Number(1) | "object" | wrapper objects, avoid them |
In plain JavaScript, typeof on a variable that was never declared returns "undefined" instead of throwing. In TypeScript that code does not compile: Cannot find name 'notDeclared'. (TS2304).
Narrowing With typeof
A typeof comparison in an if, a switch, a ternary or after && narrows the variable in each branch. It is the standard way to handle a union of primitives, and the first check to reach for on unknown.
typeof x !== "string" narrows the other way, to everything except string. And the compiler knows the eight possible strings, so a typo in the comparison is a compile error rather than a check that is silently always false:
The compiler prints index.ts(3,7): error TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"strnig"' have no overlap. In plain JavaScript the typo would quietly send every string down the number path. Fix the spelling and it prints text.
The typeof null Trap
Because typeof null is "object", TypeScript narrows typeof x === "object" to object | null, not object. Add a null check, or reading a property is an error (TS18047 'x' is possibly 'null'.).
typeof cannot tell arrays, dates, maps and plain objects apart: all of them are "object". Use Array.isArray(x) for arrays and x instanceof Date for class instances. Neither typeof nor anything else can check whether a value matches an interface at run time, because interfaces are erased; for that, write a type guard function that checks the properties.
typeof in Type Positions
In a type, typeof someVariable copies the type TypeScript inferred (or you declared) for that variable. It saves writing a type by hand for a value that already exists, and keeps the two in sync.
const theme = {
primary: "#3178c6",
spacing: 8,
dark: false,
};
type Theme = typeof theme;
// { primary: string; spacing: number; dark: boolean }
function withSpacing(t: Theme, factor: number): Theme {
return { ...t, spacing: t.spacing * factor };
}
console.log(withSpacing(theme, 2)); // { primary: '#3178c6', spacing: 16, dark: false }
let userName = "Ada";
const fixed = "Ada";
type A = typeof userName; // string
type B = typeof fixed; // "Ada" (a const keeps its literal type)
The result depends on how the variable was declared: a let or an object property is widened (string, number), a const primitive keeps its literal type. Add as const to an object or array and typeof gives readonly literal types all the way down. typeof also accepts property access, typeof theme.spacing is number, but not arbitrary expressions: type T = typeof getTheme(); is a syntax error (';' expected., TS1005). For the type a call returns, use ReturnType<typeof getTheme>.
keyof typeof: Keys of an Object as a Type
keyof typeof obj is the most common combination. typeof turns the object into a type, keyof takes its keys, and you get a union of the property names that stays correct as the object changes.
The same trick on a constant array gives a union of its values: with const roles = ["admin", "editor"] as const, the type (typeof roles)[number] is "admin" | "editor". Without as const it would only be string. The keyof page covers keyof on its own.
ReturnType<typeof fn> and typeof on Classes
Utility types such as ReturnType and Parameters take a function type. A function name is a value, so pass it through typeof first:
Writing ReturnType<createUser> fails with 'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'? (TS2749), which is the compiler asking for exactly this.
Classes are the one case where a name is both a value and a type. User as a type means an instance of the class; typeof User means the class itself, the constructor, including its static members:
class Point {
static origin = new Point(0, 0);
constructor(public x: number, public y: number) {}
}
const p: Point = new Point(1, 2); // an instance
const Ctor: typeof Point = Point; // the class (constructor + statics)
const q = new Ctor(3, 4); // q: Point
type Instance = InstanceType<typeof Point>; // Point
typeof vs instanceof vs a Type Guard
| Check | Works on | Example |
|---|---|---|
typeof | primitives, functions, "is it an object" | typeof x === "number" |
Array.isArray | arrays | Array.isArray(x) |
instanceof | class instances (Date, Error, your classes) | x instanceof Date |
in | object unions, by property | "email" in x |
| Type guard function | interfaces, type aliases, anything else | isUser(x) with x is User |
Pick the narrowest tool that fits. For class instances see instanceof; for shapes described by an interface, a type guard function is the only runtime check available.
Frequently Asked Questions
What does typeof do in TypeScript?
Two different things depending on where it appears. In an expression, typeof x is the JavaScript operator: it runs and returns a string such as "string" or "object", and TypeScript narrows x when you compare that string. In a type annotation, typeof x is a type query: it is evaluated by the compiler and gives the static type of the variable x, and it disappears from the emitted JavaScript.
What is keyof typeof in TypeScript?
keyof typeof obj gives the union of an object's property names as string literal types. typeof obj turns the object value into its type, and keyof takes the keys of that type. For const colors = { red: "#f00", blue: "#00f" }, keyof typeof colors is "red" | "blue".
Why does typeof null return "object"?
It is a bug from the first version of JavaScript that can no longer be fixed without breaking the web. TypeScript models it: after typeof x === "object", x is narrowed to object | null, so you must also check x !== null before reading properties.
How do I check if a value is an array in TypeScript?
Use Array.isArray(value), which narrows to an array type. typeof cannot do it: typeof [] is "object", the same as for plain objects and null.
What is the difference between typeof and instanceof in TypeScript?
typeof checks the primitive category of a value ("string", "number", "function", "object"...). instanceof checks whether an object was created by a particular class or constructor, such as Date or your own class User. Use typeof for primitives and instanceof for class instances; neither can check an interface or type alias, which do not exist at run time.