Typorithm · hard

4. infer, ReturnType and awaited results

typescriptstable-typescriptinterviewindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

4. infer, ReturnType and awaited results

0%

Concept and contract

Extract type structure without confusing a static pattern with runtime introspection.

infer introduces a type variable while matching a structure inside a conditional type. It can capture an array element, tuple position, function return or promise payload. The captured variable is available in the true branch where the match justified it.

Use the built-in utility when it already expresses the intended operation. A custom alias is useful for teaching or a genuinely different contract, but duplicating ReturnType or Awaited incompletely can introduce surprising differences around overloads, unions and nested thenables.

Mechanism and worked example

The example extracts the element from a readonly array and the final payload of a nested promise type through Awaited.

The example extracts the element from a readonly array and the final payload of a nested promise type through Awaited. Matching readonly arrays accepts both mutable arrays and readonly tuples. Matching only mutable Array<infer U> would miss readonly inputs.

ReturnType observes a function’s type, not the result of executing it. For overloaded functions, inference uses the last signature rather than performing overload resolution from an imaginary call. Preserve the public overload contract when designing wrappers that depend on these utilities.

Complete worked example

typescript

type ElementOf<T> = T extends readonly (infer U)[] ? U : never;
type Payload = Awaited<Promise<Promise<{ id: string }>>>;
const item: ElementOf<readonly string[]> = "QA";
const payload: Payload = { id: "a" };
Reasoning
The readonly pattern preserves array compatibility while Awaited recursively models successful resolution.

Counterexamples and common mistakes

The rejected numeric assignment shows that the extracted string element type remains specific.

The rejected numeric assignment shows that the extracted string element type remains specific. Replacing a failed conditional branch with any would make this regression invisible. Choose never or another documented fallback according to what an unmatched input should mean.

Do not assume Awaited makes an actual promise resolve or catches rejection. It describes the successful resolved type. Runtime rejection, cancellation and malformed resolved data remain separate concerns for the API and concurrency lessons.

Rejected counterexample: expected compiler diagnostic

typescript

type ElementOf<T> = T extends readonly (infer U)[] ? U : never;
// @ts-expect-error The extracted element is string.
const invalid: ElementOf<readonly string[]> = 3;
Reasoning
Extraction retains string rather than widening the result to any.

Independent practice

Write Head<T> for readonly nonempty tuples and PromiseValue<T> using Awaited.

Write Head<T> for readonly nonempty tuples and PromiseValue<T> using Awaited. Head should return never for an empty tuple or an arbitrary array whose first element is not guaranteed. Verify a literal pair and a nested promise payload.

Include a union input so you can explain distribution. Add one rejected assignment for the extracted literal. Keep the distinction between a known nonempty tuple and a possibly empty array explicit instead of asserting that every array has a first element.

Step 1: Match a required first tuple position. Step 2: Use Awaited for the promise payload.

Practice starter: solve before reading the solution

typescript

type Head<T> = unknown;
type PromiseValue<T> = unknown;
Reasoning
Require a tuple’s first position instead of pretending every array is nonempty.
  • Hint 1: The tuple pattern should accept readonly input.
  • Hint 2: An arbitrary array is not guaranteed nonempty.

Solution, tests and trade-offs

The solution matches readonly [infer H, ...unknown[]], which encodes a required first position.

The solution matches readonly [infer H, ...unknown[]], which encodes a required first position. Awaited handles the nested promise contract without recreating recursive thenable rules. The resulting aliases have no executable body because their purpose is static extraction.

Compile positive and negative receiving assignments. If you also write a runtime head function, give it its own empty-input policy and tests. A type alias returning never does not throw for a runtime empty array; it merely describes an impossible static result.

Practice solution and acceptance checks

typescript

type Head<T> = T extends readonly [infer H, ...unknown[]] ? H : never;
type PromiseValue<T> = Awaited<T>;
const head: Head<readonly ["first", 2]> = "first";
const resolved: PromiseValue<Promise<Promise<number>>> = 3;
type Empty = Head<readonly []>;

// Acceptance checks
if(head!=="first"||resolved!==3)throw new Error("inference");
Reasoning
The pattern establishes a definite first type; Awaited supplies the maintained promise transformation.

Interview reasoning and next step

In interviews, explain the scope of an inferred variable and derive one tuple or function example.

In interviews, explain the scope of an inferred variable and derive one tuple or function example. Mention readonly matching and overload behavior when relevant. Avoid presenting infer as a way to discover arbitrary runtime values.

Continue with mapped types, where keyof and extracted property relationships become a systematic transformation across an object’s fields rather than one manually written alias per property.

Optional stretch — after the core task works: Test Head on a union of two tuples.

Extract type structure without confusing a static pattern with runtime introspection.