In TypeScript, a function `createUser` returns an object literal whose shape you never declared. How do you get a named type for that return value without writing the shape twice, and why does `ReturnType<createUser>` fail to compile?
answer
- values and types are separate namespaces
- a function name is only a value
- bridge with a type query
- the utility needs a function type, not a value
- typeof f, then unwrap the return
basics
~10 sReturnType<typeof createUser> gives the type. In a type position, typeof turns the value createUser into its function type, which ReturnType then unwraps. ReturnType<createUser> fails because createUser is a value, not a type.
solid answer
~40 sWrite `type User = ReturnType<typeof createUser>`. TypeScript keeps two separate namespaces: a function declaration puts `createUser` into the *value* namespace only, so using the bare name in a type position gives "'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'?". The `typeof` type query bridges the two — it takes a value and yields its type, here the function's signature — and `ReturnType<T>` is a built-in utility constrained to `(...args: any) => any` that pulls out the return type. The payoff is a single source of truth: change what `createUser` returns and every consumer of `User` updates automatically. All of this is erased at compile time; the emitted JavaScript is unchanged.
go deeper
Be able to write ReturnType<typeof myFn> from memory and say plainly that the bare function name is a value, so typeof is required to reach its type.
Explain the two namespaces, that ReturnType is an ordinary conditional type constrained to (...args: any) => any, and why the inferred return type is widened rather than literal.
Show where deriving is worth it — store state, factory results, keeping consumers in step with the implementation — and where a hand-written declared type at a module boundary is the safer contract.
Own the policy: which types in the codebase are derived from implementations and which are declared and enforced onto them, and what that choice does to refactor blast radius and error-message quality.
## Two namespaces in one file TypeScript maintains two separate namespaces over the same source. The **value namespace** holds things that exist when the program runs: `const`, `let`, `function`, and the constructor half of a `class`. The **type namespace** holds things that exist only during checking and are erased before emit: `interface`, `type` aliases, type parameters, and the instance half of a `class`. A function declaration puts its name into the value namespace *only*. So when you write `ReturnType<createUser>`, you are asking the checker to look up a **type** called `createUser`, and there isn't one. The compiler is explicit about the fix: ```ts function createUser() { return { id: 1, name: "Ada", role: "admin" }; } type User = ReturnType<createUser>; // Error: 'createUser' refers to a value, but is being used as a type here. // Did you mean 'typeof createUser'? ``` ## What `typeof` means in a type position In a type position, `typeof x` is a **type query**: it takes a value and produces that value's static type. It is a different construct from the runtime `typeof` operator that evaluates to the strings `"string"`, `"object"` and friends — same keyword, different namespace, and the type query never survives compilation. `typeof createUser` is the type `() => { id: number; name: string; role: string }`. The operand of a type query is restricted. It must be an identifier or a dotted path to a value — `typeof store`, `typeof store.getState`, or `typeof import("./config")` — not an arbitrary expression. `typeof createUser()` is not legal; you cannot call something in a type position, because nothing in the type layer runs. ## What `ReturnType` actually is `ReturnType` is not compiler magic; it is a one-line conditional type shipped in TypeScript's own library: ```ts type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any; ``` The constraint `T extends (...args: any) => any` is exactly why the `typeof` is mandatory: the argument must already be a **function type**. Hand it a value and the lookup fails before the constraint is even considered. So the composite `ReturnType<typeof createUser>` reads right-to-left: take the value `createUser`, ask for its type (a function signature), then extract the return type from that signature. ## Why derive instead of declare The alternative is to declare the shape by hand and annotate the function with it. That is the right call at a published API boundary. Inside a module, though, hand-written duplicates drift: someone adds a field to the returned object, forgets the interface, and the two disagree until a bug surfaces. Deriving makes the implementation the single source of truth, which is why the idiom is everywhere in real codebases — most famously for store state: ```ts type RootState = ReturnType<typeof store.getState>; ``` ## The widening gotcha A derived type reflects what the checker **inferred**, not what you see in the source. Object literals returned from a function get their property types widened to the general primitive: ```ts function createUser() { return { id: 1, role: "admin" }; } type User = ReturnType<typeof createUser>; // { id: number; role: string } — not { id: 1; role: "admin" } ``` If you wanted the literal types, the fix belongs at the function — return the object with a `const` assertion — not at the `ReturnType` call site. Candidates who expect `role` to be `"admin"` here have not internalised that inference, not the utility, decides the answer. ## Nothing happens at runtime A final point interviewers listen for: `ReturnType<typeof createUser>` does not call `createUser`, does not inspect it, and costs nothing. Types are erased — the utility resolves entirely during checking, and the emitted JavaScript is byte-identical whether you wrote the type by hand or derived it.
- If `createUser` returns `{ role: "admin" }`, why is the derived type `{ role: string }`, and how would you keep the literal?Object literal properties widen during inference, so the checker records `string`. `ReturnType` faithfully reports what was inferred. To keep the literal, fix it at the source — return the object with an `as const` assertion, or annotate the function's return type explicitly. Nothing you do at the `ReturnType` call site can un-widen a type that was already widened.
- Can you write `ReturnType<typeof createUser()>` to skip a step?No. A type query's operand must be an identifier or a dotted path to a value, never an expression — calls are not permitted, because nothing in the type layer executes. `typeof createUser` already yields the full signature, and `ReturnType` extracts the return type from it, so there is nothing to skip.
- Does `typeof` in a type position emit any JavaScript?None. It is a compile-time type query that is erased with the rest of the type annotations. It shares a keyword with the runtime `typeof` operator, but they live in different namespaces: one asks the checker for a value's static type, the other evaluates at runtime to a string. Deriving a type has zero runtime cost.
saying these in an interview costs you the question
- Writes ReturnType<createUser> without the typeof
- Thinks the type query runs or calls the function
- Says typeof in a type position emits a runtime check
- Expects literal types where inference widened them
- Cannot say why a function name is not a type