Topic 8.2
typeof in Types
In one line
Inside a type, typeof value asks the compiler "what type does this value have?" so you can derive types from real values and configs instead of writing them twice.
Think of it like this
You have a finished LEGO castle and want a box that fits it exactly. You could measure every tower and write the sizes down, or you could place the castle on a scale and read the result off. **typeof in a type reads the shape off a real value** instead of making you describe it again.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- typeof (in a type)
- A compile-time question: what type does this value have? It is erased when the program runs.
- typeof (at runtime)
- The JavaScript operator that returns text like "string" or "object" while the program is running.
- Type position
- A place where the compiler expects a type, like after a colon or after
type Name =. - Source of truth
- The one place a fact is written, so everything else is derived from it.
- as const
- A suffix that freezes a value into its exact literal types and makes its properties read-only.
- Widening
- When TypeScript turns the literal
5into the broader typenumberbecause the variable could change. - ReturnType
- A built-in helper that gives the type a function returns.
- Constructor type
- The type of the class itself (what
newis called on), writtentypeof ClassName. - Instance type
- The type of an object made with
new, written as the class name.
Step by step
01Two typeofs, two worlds
Both of these lines say typeof origin, and they do different jobs. The first is inside a type: the compiler works out the shape of origin and names it Origin. The second is an ordinary JavaScript expression: it runs and prints object.
const origin = { x: 0, y: 0, label: "start" };
type Origin = typeof origin; // type world: { x: number; y: number; label: string }
console.log(typeof origin); // value world: prints "object"02Keep one copy
If you add z: 0 to origin, Origin gains z: number on its own. Every function that takes an Origin now expects a z. There is no second definition to forget.
03Configs and as const
A plain object literal gets widened: env: "production" has the type string, because the property could be changed later. Adding as const freezes everything: env is exactly "production", port is exactly 8080, and the array becomes a read-only tuple.
Now index into it: (typeof strict)["env"] is "production", and (typeof strict.features)[number] is "search" | "cart", a union of the real list members. Topic 8.3 explains the square brackets.
04Names and values of a constant object
A common pattern replaces an enum (Topic 5.3) with a frozen object. keyof typeof Status gives the key names ("Idle" | "Loading" | "Done"). Indexing the object type with those names gives the values ("idle" | "loading" | "done").
const Status = { Idle: "idle", Loading: "loading", Done: "done" } as const;
type StatusName = keyof typeof Status; // "Idle" | "Loading" | "Done"
type StatusValue = (typeof Status)[StatusName]; // "idle" | "loading" | "done"05Functions and classes
typeof makeUser is the type of the function. ReturnType and Parameters read it apart. No function is called while the compiler does this.
For a class, Counter is the instance type and typeof Counter is the class itself. A function that takes the class as an argument and calls new on it needs typeof Counter.
06Only names go after typeof
typeof fetchCount() is rejected as a syntax error, because a call is an expression the compiler won't run. Write ReturnType<typeof fetchCount> instead.
Try it yourself
- 1
Grow the value
In the first example add
z: 0toorigin. Predict the new error at thecopyline before you read it, then thewrongline.copynow lacksz(TS2741, line 17), andwronglacks bothzandlabel(TS2739, line 18). Nothing else was edited:Origingrew by itself. - 2
Remove as const
In the fourth example delete
as const. Predict the newStatusValue. The answer isstring: the exact values are gone, andlabel("paused")stops being an error.StatusNameis unchanged, because key names don't depend onas const. - 3
Ask for a nested path
In the second example add
type Port = typeof strict.port;and hover it: it is8080. Then trytype Bad = typeof strict.nope;: the error isTS2339: Property 'nope' does not exist on typefollowed by the whole frozen object type.
Code & diagrams
Count is number but Limit is 5: a let can change, a const can't. The last console.log is the runtime typeof, which prints text. The last line is an error because Origin, read off the value, needs a label.
Console
(nothing was printed)Expected console output
{ x: 5, y: 0, label: 'start' }
objectWithout as const the values are widened to string and number. With it, everything is exact and read-only, so Feature is a union of the two real list members and "checkout" is rejected.
Console
(nothing was printed)makeUser has no declared return type, yet User knows its shape because ReturnType reads it. Parameters comes back as a labelled tuple. Counter is the instance type; typeof Counter is the class, which is what make() needs to call new.
Console
(nothing was printed)Expected console output
{ name: 'Asha', age: 30, createdAt: '2026-10-12' }
Counter { count: 0 }Add Failed: "failed" to Status and both types grow by themselves. Note the last line: TypeScript rejects "paused" but the code still runs and prints PAUSED.
Console
(nothing was printed)Expected console output
LOADING
PAUSEDBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use typeof on a type
At the end of the first example add type Bad = typeof Origin;.
Break #2
Try to call a function inside typeof
Add type Wrong = typeof makeUser("A", 1); to the third example.
Myth vs fact
Myth
typeof x in a type does the same as typeof x in code.
Fact
They only share a spelling. The one in a type is answered by the compiler and erased; the one in code runs and returns text like "object".
Myth
typeof in a type calls the function you give it.
Fact
Nothing is called or run. typeof fn is the type of the function itself.
Myth
typeof Counter and Counter are the same type for a class.
Fact
Counter is the type of an instance; typeof Counter is the class itself, including new and statics.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
typeofin a type is a type query. The compiler resolves the name in the value namespace, and the result is the declared or inferred type of that symbol. It accepts a dotted path (typeof strict.features, as in the second example) but never a call or any other expression. - ▸
Because the type is read from the value, an unannotated value can't be defined in terms of its own type:
const a = { next: null as unknown as typeof a };fails withTS7022: 'a' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. Write one annotation (for exampletype ListNode = { next: ListNode | null }andconst a: ListNode = ...) to break the cycle. - ▸
as constobjects are the current alternative toenumin code that must be erasable (Topic 5.3): there is no extra runtime object generated beyond your own constant. UndererasableSyntaxOnly(TypeScript 5.8) real enums are not allowed at all.
Remember this
- 1
The problem this solves: you already have a value (a settings object, a function, a class) and need its type in a signature or alias, and you don't want to write that shape out a second time. JavaScript already has a
typeofoperator that runs in your program and returns text like"string"or"object". TypeScript reuses the same word in a second place: in a type position (after a colon, aftertype X =, inside angle brackets),typeof valueis evaluated by the compiler, not at runtime, and gives the type of that value. The two share a name but live in different worlds: the runtime one gives text, the type one gives a type, and the type one disappears when the program runs. - 2
The value must really exist as a variable, a function or a class.
typeof originis fine;typeof PointwherePointis only atypeis an error ('Point' only refers to a type, but is being used as a value here), because types aren't values and have no type of their own to ask about. - 3
Why use it? When you already have a value, say a settings object, writing a second
type Settings = { ... }by hand creates two copies that can drift apart.type Settings = typeof settingsmakes the value the source of truth. The type is whatever the value says it is, including theletversusconstwidening rules from Topic 1.3:typeofalet n = 5isnumber, buttypeofaconst m = 5is5. - 4
The best partner is
as const(Topic 4.2): it freezes an object into its exact literal values and makes itreadonly. Thentypeof confighas the exact strings and numbers, andkeyof typeof configgives the config's key names as a union. The pairkeyof typeof Statusis so common you'll type it without thinking.(typeof Status)[StatusName]gives all the values. The parentheses are optional (typeof Status[StatusName]means the same thing, checked), but they make the reading clear, and many style guides add them. - 5
typeofalso works on functions, which is how you get the types of a function you didn't write or didn't want to declare twice.ReturnType<typeof makeUser>is the type the function returns, andParameters<typeof makeUser>is a tuple of its parameter types. Both are built-in helper types you'll see built from scratch in Topic 9.3 and listed in Topic 10.2. No function call is made:typeof makeUseris about the function itself. - 6
With a class, there are two types: the type of its instances (written
Counter) and the type of the class itself, the constructor with its static members (writtentypeof Counter). If a function receives the class (so it can callnew C()), its parameter type istypeof Counter.InstanceType<typeof Counter>turns the class type back into the instance type. - 7
You can't put an expression into
typeofin a type.typeof fetchCount()is a syntax error, because a call is something that runs. Only names and property paths liketypeof config.server.portare allowed. To get what a call returns, useReturnType<typeof fetchCount>.
Explain it without notes
What is the difference between typeof x in a type and typeof x in an expression?
How do you get the union of a constant object's keys, and the union of its values?
How do you get the return type of a function without writing it twice?
What is the difference between Counter and typeof Counter for a class?
Practice
Given const sizes = { small: 1, medium: 2, large: 3 } as const;, write the types SizeName and SizeValue, and a function price(size: SizeName): SizeValue.
Given a function function loadUser() { return { id: 1, name: "Asha", admin: false }; }, declare a variable u whose type is the returned value, without writing the object shape by hand.
Trade-offs
- ↔
Deriving types from values keeps one source of truth, but a reader has to follow the chain
typeof->keyof-> index to see the final shape. Hover the alias in your editor, or add a// ^?while learning, and give long chains a name. - ↔
as constmakes values exact and read-only. That is what you want for configs and lookup tables, and too strict for data you plan to change, where a normal annotation is better.
Done when you can
I can explain the difference between
typeof xin a type and in code.I can turn a constant object into a type with
typeof.I can write
keyof typeof objand(typeof obj)[keyof typeof obj]and say what each gives.I can use
ReturnType<typeof fn>andParameters<typeof fn>.I can tell
Counterfromtypeof Counter.