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) for valueExpression.
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 x to the declaration that introduced it (a const/let binding, 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 const is used).
  • It substitutes that resolved type everywhere typeof x appears, exactly as if you had written the type out yourself.
  • Any surrounding type operators (keyof, ReturnType<>, Parameters<>, indexed access like typeof 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 typeof whenever you already have a representative value (a config object, default props, a mock response) instead of hand-authoring a duplicate interface that can drift out of sync.
  • Pair typeof with as const whenever you need the derived type to keep literal values (specific strings/numbers) rather than their widened base types.
  • Use keyof typeof obj to build a type-safe union of an object’s keys for lookup tables, enums-as-objects, and dictionaries.
  • Use ReturnType<typeof fn> and Parameters<typeof fn> to capture a function’s inferred signature instead of retyping it, so refactors of the function automatically propagate.
  • Remember typeof only 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 interface is often clearer for readers than a chain of typeof/keyof/ReturnType.

Practice Exercises

  • Declare a const settings object with theme, fontSize, and notificationsEnabled fields. Derive a Settings type using typeof, then write a function updateSettings(current: Settings, changes: Partial<Settings>): Settings that returns a merged copy.
  • Create a const httpStatusMessages object mapping status codes like "200", "404", "500" to human-readable messages (use as const). Use keyof typeof httpStatusMessages to type a getMessage(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) }. Derive type Point3 = ReturnType<typeof createPoint> and write a second function that accepts a Point3 and logs its magnitude.

Summary

  • typeof has 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 value extracts 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.
  • typeof is frequently combined with keyof (key unions), ReturnType/Parameters (function signatures), and as 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.