TypeScript typeof Type Operator
TypeScript reuses the familiar JavaScript keyword typeof for a second, completely different job: when it appears inside a type position (a type annotation, a type alias, a generic argument), it asks the compiler "what is the static type of this value?" instead of asking JavaScript "what is the runtime tag of this value?" This lets you derive types directly from values you already have — objects, functions, class instances — instead of writing a second, parallel type declaration that can drift out of sync with the implementation.
Overview / How it works
JavaScript’s typeof is a runtime operator: typeof 42 evaluates to the string "number". TypeScript adds a second, unrelated meaning for the same keyword that only exists at compile time and only makes sense inside a type position. When you write type T = typeof someValue;, the compiler looks up the declared or inferred type of someValue at that point in the code and uses it as the definition of T. No code runs, no string is produced — this is purely a query against the compiler’s internal type information.
The context tells TypeScript which meaning you intend. Inside an expression (e.g. if (typeof x === "string")) it is the JavaScript runtime operator. Inside a type annotation, a type alias’s right-hand side, or a generic type argument like ReturnType<typeof fn>, it is the TypeScript type operator. The two never collide because the parser always knows, from surrounding syntax, whether it is parsing an expression or a type.
This matters because TypeScript is structurally typed: two values are type-compatible if they have the same shape, regardless of name. typeof leans directly into that — instead of hand-writing an interface that must be kept in sync with an object literal, you let the compiler read the shape off the object itself. This is especially valuable for configuration objects, default values, API response shapes inferred from a mock, and function signatures you don’t want to duplicate.
One important restriction: typeof only works on values that are in scope at that point in the code — variables, functions, classes (as constructors), and properties reached via dot access. It cannot be applied to a type alias or an interface name, because those don’t exist as values; they are erased before your program ever runs.
Syntax
type AliasName = typeof valueExpression;
typeof— the type operator, written to the left of a value expression, used only inside a type position.valueExpression— an identifier or property access that refers to a variable, function, class, or object property currently in scope (e.g.myVar,myObj.prop,myFunction).AliasName— the new type you’re defining, which becomes exactly the static type TypeScript has inferred (or you declared) forvalueExpression.
const example = { a: 1, b: "two" };
type ExampleType = typeof example;
const copy: ExampleType = { a: 10, b: "twenty" };
console.log(copy);
Output:
{ a: 10, b: 'twenty' }
Here ExampleType becomes { a: number; b: string } — the compiler widened the literal values 1 and "two" to their base types, because example was declared with plain property assignments, not literal types.
Examples
Example 1: Deriving an object type from a value
const user = {
id: 1,
name: "Ada Lovelace",
isAdmin: false,
};
type User = typeof user;
const admin: User = {
id: 2,
name: "Grace Hopper",
isAdmin: true,
};
console.log(admin);
Output:
{ id: 2, name: 'Grace Hopper', isAdmin: true }
Instead of writing interface User { id: number; name: string; isAdmin: boolean } by hand, typeof user reads that shape directly off an existing object. If a field is later added to user, User updates automatically — there is nothing to forget to keep in sync.
Example 2: Combining typeof with keyof for a safe lookup
const colors = {
red: "#ff0000",
green: "#00ff00",
blue: "#0000ff",
} as const;
type ColorName = keyof typeof colors;
function getColor(name: ColorName): string {
return colors[name];
}
console.log(getColor("green"));
Output:
#00ff00
typeof colors gets the object’s type, and keyof then extracts the union of its property names: "red" | "green" | "blue". getColor can now only be called with a key that actually exists on colors — calling getColor("purple") is a compile error. This keyof typeof obj pairing is one of the most common idioms in TypeScript code.
Example 3: Capturing a function’s signature and return type
function createUser(name: string, age: number) {
return {
name,
age,
createdAt: new Date().toISOString(),
};
}
type CreateUserFn = typeof createUser;
type NewUser = ReturnType;
const buildUser: CreateUserFn = (name, age) => {
return { name, age, createdAt: "now" };
};
const u: NewUser = buildUser("Alan Turing", 41);
console.log(u.name, u.age);
Output:
Alan Turing 41
typeof createUser captures the whole function type, (name: string, age: number) => { name: string; age: number; createdAt: string }, which is reused to type buildUser so it must match the same signature. ReturnType<typeof createUser> goes one step further and pulls out just the object shape the function returns, without writing that shape out by hand.
Under the Hood
When the compiler encounters typeof x in a type position, it performs a small, purely compile-time resolution:
- It resolves
xto the declaration that introduced it (aconst/letbinding, a function declaration, a class, or a property access chain). - It reads that declaration’s type — either an explicit annotation, or the type TypeScript already inferred for it (with the usual widening rules: object literal properties widen from literals to their base types unless
as constis used). - It substitutes that resolved type everywhere
typeof xappears, exactly as if you had written the type out yourself. - Any surrounding type operators (
keyof,ReturnType<>,Parameters<>, indexed access liketypeof obj["key"]) then operate on that resolved type.
Crucially, none of this produces runtime code. TypeScript’s type system is fully erased during compilation: every type alias, every typeof-derived type, every generic parameter disappears from the emitted JavaScript. The compiled output for the examples above contains only the plain variable declarations, function bodies, and console.log calls — there is no trace at runtime that ColorName or NewUser ever existed. This is also why typeof in type position can never affect what your program actually does; it only affects what the compiler will accept.
Common Mistakes
Mistake 1: Using typeof on a type name instead of a value
typeof only works on values. Interfaces and type aliases are erased before runtime and have no value form, so this fails to compile:
interface Point {
x: number;
y: number;
}
type P = typeof Point;
tsc error: 'Point' only refers to a type, but is being used as a value here.
The fix is simply to reference the type directly — there is no need for typeof when you already have a type, only when you have a value:
interface Point {
x: number;
y: number;
}
type P = Point;
const p: P = { x: 1, y: 2 };
console.log(p);
Output:
{ x: 1, y: 2 }
Mistake 2: Forgetting as const, losing literal types
Object literal properties widen to their base type (string, number, etc.) unless you lock them down with as const. This bites people who expect typeof to preserve a specific literal:
const config = {
mode: "production",
};
type Mode = typeof config.mode;
function applyMode(mode: "production" | "development") {
console.log(mode);
}
applyMode(config.mode);
tsc error: Argument of type 'string' is not assignable to parameter of type '"production" | "development"'.
config.mode widened to plain string, which is incompatible with the narrow literal union applyMode expects. Adding as const preserves the literal type all the way through typeof:
const config = {
mode: "production",
} as const;
type Mode = typeof config.mode;
function applyMode(mode: "production" | "development") {
console.log(`Applying mode: ${mode}`);
}
applyMode(config.mode);
Output:
Applying mode: production
Best Practices
- Reach for
typeofwhenever you already have a representative value (a config object, default props, a mock response) instead of hand-authoring a duplicateinterfacethat can drift out of sync. - Pair
typeofwithas constwhenever you need the derived type to keep literal values (specific strings/numbers) rather than their widened base types. - Use
keyof typeof objto build a type-safe union of an object’s keys for lookup tables, enums-as-objects, and dictionaries. - Use
ReturnType<typeof fn>andParameters<typeof fn>to capture a function’s inferred signature instead of retyping it, so refactors of the function automatically propagate. - Remember
typeofonly sees values in scope — it cannot be applied to interfaces, type aliases, or values not yet declared at that point in the file. - Don’t over-derive: if the runtime value’s shape is incidental or likely to change in ways unrelated to your type contract, an explicit
interfaceis often clearer for readers than a chain oftypeof/keyof/ReturnType.
Practice Exercises
- Declare a
const settingsobject withtheme,fontSize, andnotificationsEnabledfields. Derive aSettingstype usingtypeof, then write a functionupdateSettings(current: Settings, changes: Partial<Settings>): Settingsthat returns a merged copy. - Create a
const httpStatusMessagesobject mapping status codes like"200","404","500"to human-readable messages (useas const). Usekeyof typeof httpStatusMessagesto type agetMessage(code)function that only accepts known codes. - Write a function
createPoint(x: number, y: number)that returns{ x, y, magnitude: Math.sqrt(x * x + y * y) }. Derivetype Point3 = ReturnType<typeof createPoint>and write a second function that accepts aPoint3and logs itsmagnitude.
Summary
typeofhas two meanings in TypeScript: the familiar JavaScript runtime operator inside expressions, and a compile-time type operator inside type positions.- In a type position,
typeof valueextracts the compiler’s resolved (declared or inferred) type of that value, letting you derive types instead of duplicating them. - It only works on values in scope — not on interfaces or type aliases, which have no runtime form.
typeofis frequently combined withkeyof(key unions),ReturnType/Parameters(function signatures), andas const(preserving literal types instead of widened base types).- All type information, including anything derived via
typeof, is erased during compilation — the emitted JavaScript has no trace of it and behaves identically with or without it.
