Literal Inference & NoInfer

The Course
•
4 min read

Intermediate introduced as const. A const type parameter applies a similar literal-preservation preference to an inferred call argument. In function define<const T>(value:T):T, an inline literal can retain its precise readonly shape. This is a checking decision, not Object.freeze.

Preserve a literal before controlling inference

Compare ordinary generic inference with const inference on an inline record. The ordinary record’s mode widens to string; the const version retains the literal “safe” and a readonly property. An annotation of exactly “safe” lets you check the difference without printing a type.

See literal inferenceTypeScript
function remember<T>(value: T): T {
  return value;
}

function keep<const T>(value: T): T {
  return value;
}

const ordinary = remember({ mode: "safe" });
const precise = keep({ mode: "safe" });

ordinary.mode = "fast";

const exact: "safe" = precise.mode;

console.log(ordinary.mode, exact);

Enable JavaScript to edit and type-check this example.

Check your prediction
fast safe

Choose which argument supplies evidence

Inference can also gather evidence from more than one argument. If the first argument supplies allowed modes and the second is a default, you may want only the allowed list to determine T. Built-in NoInfer<T> prevents that second position from contributing candidates, while still checking its compatibility with T.

Without NoInfer, an incompatible fallback can help widen T. For example, a helper with fallback: T can infer “safe” | “fast” | “typo” from pick(["safe", "fast"], "typo"). That compiles because inference considered both arguments. Our desired rule is stricter: the first argument defines the choices, and the second must fit them. The error lab tests that rule.

Read the pickMode signature in order: T must be a string, allowed provides readonly choices, and fallback must be one of the inferred choices. The runtime includes check remains useful for untyped callers or invalid assertions. NoInfer does not validate an arbitrary value from JSON.

These features are available in the pinned TypeScript 5.9.3 compiler. Use them when they improve a real caller contract; an ordinary annotation is often enough. Passing an already widened string[] cannot recover its original literal members by adding const to a later function.

Predict, check, and run

Write the output before running. Use Check types first, then Check & run. Types disappear at runtime: explain both what the compiler checks and what the code actually does. Each editor is an independent ES module; await asynchronous work explicitly.

Predict, then runTypeScript
function pickMode<const T extends string>(
  allowed: readonly T[],
  fallback: NoInfer<T>
): T {
  if (!allowed.includes(fallback)) {
    throw new Error("Invalid fallback");
  }

  return fallback;
}

console.log(pickMode(["safe", "fast"], "safe"));

Enable JavaScript to edit and type-check this example.

Check your prediction
safe

Read the compiler error

Predict the rejected relationship before checking. Read the real diagnostic and its source location; fix the contract or the value instead of hiding it with an assertion.

Find and fix the type errorTypeScript
function choose<const T extends string>(
  choices: readonly T[],
  fallback: NoInfer<T>
): T {
  return fallback;
}

console.log(choose(["small", "large"], "medium"));

Enable JavaScript to edit and type-check this example.

Check your diagnosis

The first argument determines small|large. NoInfer stops medium from expanding that set through the fallback argument.

Build it yourself

Exercise

Literal Inference & NoInfer: practise

Write choose<const T extends string> with readonly choices and a NoInfer<T> fallback. Choose small from [small,large], print small, then verify medium is a compiler error. Keep a runtime membership check.

Your workspaceTypeScript
// Start from the stated contract. Add a valid call and an edge case.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

Let the list determine the allowed literal union.

Hint 2: a first step

Use NoInfer only around the fallback type, not around the choices.

Show one solution
One possible solutionTypeScript
function choose<const T extends string>(
  choices: readonly T[],
  fallback: NoInfer<T>
): T {
  if (!choices.includes(fallback)) {
    throw new Error("Invalid choice");
  }

  return fallback;
}

console.log(choose(["small", "large"], "small"));

Enable JavaScript to edit and type-check this example.

small

Trace the implementation against the task before comparing styles. A passing example supports that case; use the changes below to challenge the wider contract.

Change it and test it

Assign the list to a string[] variable before calling choose and observe how the contract widens. Compare this with an as const list and explain why inference cannot recover discarded information.

Was this article helpful?

Your response is saved on this device.