Ch. 2 · TypeScript

TypeScript Const Type Parameters

Infer narrow literal and readonly types at call sites with const type parameters, and know the inference it does not change.

~3 min readintermediateupdated Oct 4, 2026

Normally a generic call widens literal arguments: passing ['a', 'b'] to a generic function infers string[], not the tuple with literal members. A const type parameter, written <const T>, tells the compiler to infer as if the argument had as const, without you writing it at every call site.

Before you start

You should be comfortable with generics, as const, and the difference between a widened type and a literal type. This article covers const parameters (TypeScript 5.0 and later); it does not change runtime behavior, only inference.

Step-by-step walkthrough

Step 1: Annotate the parameter with const

Place const before the type parameter name: function tuple<const T extends readonly unknown[]>(items: T): T. At the call site the argument is inferred in its narrowest form — string literals become "a" | "b" and the array becomes a readonly tuple — so the return type carries that detail.

Step 2: Reach for it when the literal matters

Use const parameters for builders and helpers whose return type should reflect the exact inputs: config maps, tuple factories, route tables and defineConfig-style helpers. When the caller only needs a collection type such as string[], a plain <T extends string[]> is simpler and avoids over-narrow readonly types.

Step 3: Combine with constraints, not casts

A const parameter still respects its constraint. <const T extends readonly string[]> infers a readonly tuple of specific strings while rejecting non-strings. This replaces the old pattern of demanding as const from callers or casting inside the function, and it keeps the narrow type flowing out of the call without a runtime cost.

Worked scenario

Without const, the tuple becomes a plain array; with it, the literal names survive.

function tuple<const T extends readonly unknown[]>(items: T): T {
  return items;
}

const values = tuple(['a', 'b']); // readonly ["a", "b"]
// Without `const T` this would infer string[].
TypeScript

Walk through the example

The argument ['a', 'b'] is read as a fixed two-element tuple whose members are the literal strings "a" and "b", and T is that readonly tuple. The function body is unchanged: const affects inference only, so the same object is returned at runtime. Downstream code can rely on values[0] being "a" rather than string.

Common mistake

Expecting a const parameter to deep-freeze the value or to change runtime objects. It is entirely compile-time: the array is still mutable at runtime, and mutating it produces no error. Another mistake is applying it everywhere, which spreads readonly tuple types through code that expected flexible arrays and causes avoidable friction.

Verify the behavior

Hover the returned constant and confirm it is a readonly tuple of literal types, then remove const and confirm it widens to string[]. Pass a non-literal such as a variable typed string[] and confirm the inferred type stays wide, because there is no literal to preserve. Type-check with npx tsc --noEmit after attempting to push to the returned tuple to see the readonly error.

Interview exercise

A defineRoutes helper should type the route names from the object passed in, so lookups are checked against those exact names. How does a const parameter help?

Answer and reasoning

Type the parameter as <const T extends Record<string, () => void>> and return T (or keys derived from it). The const modifier keeps the route names as literal keys instead of collapsing them to string, so a later navigate(name) can accept only keyof T. The caller writes defineRoutes({ home: ..., about: ... }) with no as const, and the exact keys flow through to type checking.

Follow-up discussion

Does const T replace as const entirely? At the call site, yes for the arguments it covers; the function’s own variables may still need as const. Is it a breaking change for existing callers? It can narrow previously widened types, so add it to a helper deliberately and check that downstream code still compiles.

Continue learning

Go deeper on widening in TypeScript literal inference and put it to work with generic constraints. Read the TypeScript 5.0 release notes and try the TypeScript interview questions.

More in TypeScript

read ✓TypeScript · hard

TypeScript Abstract Classes and Contracts

Share behavior with abstract classes, enforce required members, and decide when an interface or composition is the better contract.

~2 min readread →
esc