skip to content

In TypeScript, you need a second value — a test double or a wrapper — to have exactly the same type as an existing function called `parseConfig`. How do you express that without retyping its signature by hand?

level: middleimportance: should knowfreq 44%

answer

  1. value space and type space are separate
  2. one keyword, two positions
  3. read the type back off the value
  4. identifiers and dotted paths only
  5. no expressions, nothing is evaluated

basics

~10 s

Use a typeof type query: writing typeof parseConfig in type position yields the type the compiler already inferred for that value, including its parameters, its return type, and any properties hung on the function.

solid answer

~40 s

Write `type Parse = typeof parseConfig;`. In **type position**, `typeof` is the *type query* operator: it looks up the identifier in value space and hands back its static type — parameters, return type, and any properties the function carries. It is unrelated to the JavaScript `typeof` operator that yields a string at runtime; the two just share a keyword, and which one you get depends on whether you are in a type or a value position. A query accepts an identifier or a dotted path (`typeof config.parse`), plus the import form `typeof import('./config')` — not an arbitrary expression, so `typeof parseConfig('x')` is an error. The payoff is that the double or wrapper stays in sync automatically: change `parseConfig`'s signature and every `typeof parseConfig` annotation re-checks against the new shape instead of silently drifting.

code

typescript · 13 lines
typescript
function parseConfig(input: string, strict = false): Record<string, string> {
  return strict ? { raw: input } : {};
}

type Parse = typeof parseConfig;

// Parameters are contextually typed; no annotations needed here.
const fake: Parse = (input, strict) => ({
  received: input,
  strict: String(strict),
});

console.log(fake('{}', true));

go deeper

for a junior

Know that writing typeof someFunction in a type position gives you that function's type, so you can reuse it instead of copying the signature.

for a middle

Explain the value-space/type-space split that makes the query possible, and state its limits: identifiers and dotted paths only, values only, no expressions and no emitted code.

for a senior

Argue for the query on maintenance grounds — mocks, wrappers and adapters annotated this way break at compile time when the original signature changes, which is the failure you actually want.

for a principal

Decide the direction of truth for a codebase: whether the implementation is the source a query reads from, or whether a declared type is the contract implementations must satisfy. Queries are cheap but they let an accidental signature change propagate silently.

## Two namespaces, one keyword TypeScript keeps *values* and *types* in separate namespaces. `const parseConfig = ...` declares a value; `type Parse = ...` declares a type. The bridge between them, in the value-to-type direction, is the type query: ```ts function parseConfig(input: string, strict = false): Record<string, string> { return strict ? JSON.parse(input) : {}; } type Parse = typeof parseConfig; // (input: string, strict?: boolean) => Record<string, string> ``` The keyword is the same one JavaScript uses, but the position decides the meaning. In an expression, `typeof x` is a runtime operator producing a string. After a `:`, inside a type alias, in a generic argument — anywhere a type is expected — `typeof x` is a compile-time query that produces no code at all. ## Why you would use it The direct benefit is that a hand-written duplicate of a signature drifts. Consider a test double: ```ts const fake: typeof parseConfig = (input, strict) => ({ received: input, strict: String(strict), }); ``` Because `fake` is annotated with the query, its parameters are contextually typed — no annotations needed on the arrow — and the day someone adds a third parameter to `parseConfig` or changes its return type, `fake` fails to compile. If instead you had written `const fake: (input: string, strict?: boolean) => Record<string, string> = ...`, the copy would keep compiling against a signature that no longer exists anywhere. The same applies to wrappers, decorated versions, adapter tables, and anywhere you need "the same thing as that". ## What the query captures Everything the compiler inferred for the value, not just the callable part. If the function carries properties — the hybrid shape of a callable plus its data — the query brings them along: ```ts function tick(label: string): number { tick.count += 1; return tick.count; } tick.count = 0; type Tick = typeof tick; // callable AND { count: number } const spy: Tick = Object.assign((label: string) => 0, { count: 0 }); ``` That is a real advantage over writing the signature by hand, where the property is easy to forget. ## What the query accepts A type query takes an *entity name*: an identifier, or a dotted path of identifiers. ```ts type A = typeof parseConfig; // ok type B = typeof config.parse; // ok - dotted path type C = typeof import('./config'); // ok - the module's shape // type D = typeof parseConfig('x'); // error: not an entity name ``` The last line is the mistake people make when they actually want the *return* type. A call is an expression, and the query does not evaluate expressions — nothing is executed at compile time. The return type is obtained with a separate mechanism, not by pretending to call the function. A second restriction follows from the two-namespace rule: the operand must exist as a **value**. `typeof SomeTypeAlias` is an error, because a type alias declares nothing in value space. Conversely, some declarations exist in both namespaces — a class declares a type (its instances) and a value (the class object) — which is why `typeof` applied to a class means something quite different from the bare class name. ## Erasure, again A type query emits nothing. `type Parse = typeof parseConfig` compiles to no JavaScript, and using `Parse` as an annotation adds no check. The value `parseConfig` still has to exist at runtime for the *value* references to work, but the query itself is purely a compile-time lookup. This is why a type-only import of a value is enough to query it: the query never needs the value to be present in the emitted module. ## Interview framing Say it in one line — "`typeof` in type position reads back the type the compiler already inferred for a value" — then give the reason: it keeps mocks, wrappers and re-exports in lockstep with the original instead of duplicating a signature that will drift. Then show you know the edges: identifiers and dotted paths only, values only, and it produces no runtime code.

  • Why is `typeof` in a type position not the same thing as the JavaScript `typeof` operator?
    They share a keyword and nothing else. In an expression, `typeof x` runs at runtime and evaluates to a string like `"function"`. In a type position it is the type query operator: a compile-time lookup that yields the value's static type and emits no code. The position, not the keyword, decides which one you wrote.
  • Why does `typeof parseConfig('x')` not compile?
    A type query accepts an entity name — an identifier or a dotted path — plus the `typeof import('...')` form. A call is an expression, and nothing is evaluated at compile time, so the compiler rejects it. Someone writing this usually wants the function's return type, which comes from a separate extraction mechanism rather than from a pretend call.
  • What happens to `typeof tick` if the function later gains a property such as `tick.count`?
    The query picks it up automatically. The compiler folds properties assigned to a function declaration into that function's inferred type, so `typeof tick` becomes callable *and* carrying `count`. Anything annotated with the query must then supply the property too — which is exactly the drift protection you wanted.

saying these in an interview costs you the question

  • Confuses the type query with the runtime operator that returns a string
  • Thinks `typeof` can be applied to a call or any expression
  • Applies `typeof` to a type alias, which has no value-space entry
  • Copies the signature by hand and lets the duplicate drift
  • Assumes the query emits a runtime lookup of the function

context