Typorithm · hard

10. Const type parameters and variadic tuples

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

10. Const type parameters and variadic tuples

0%

Concept and contract

Preserve small literal sequences through reusable helpers without overcomplicating callers.

A const type parameter asks inference to preserve literal-like information for expressions supplied directly at a call site. It can help a registry or tuple helper retain exact keys without requiring each caller to write as const. This is an inference preference, not a runtime constant declaration.

Use a readonly-compatible constraint when the inferred value may be a readonly tuple. A mutable constraint can force fallback to a wider mutable type. Already widened variables cannot recover their original literal details merely by passing through a const generic helper.

Mechanism and worked example

The example preserves an inline sequence of route names as a readonly tuple.

The example preserves an inline sequence of route names as a readonly tuple. The required positions retain their literal types, allowing later code to derive a finite union of names. The helper simply returns the supplied value at runtime.

Variadic tuple types can describe operations such as prepending an element while retaining the remaining positions. Begin with a two-item example, then generalize. The type should describe an operation the implementation actually performs, rather than asserting an unrelated tuple result.

Complete worked example

typescript

function names<const T extends readonly string[]>(values: T): T { return values; }
const selected = names(["roles", "news"]);
const first: "roles" = selected[0];
Reasoning
The readonly constraint allows the inline tuple’s literal positions to survive inference.

Counterexamples and common mistakes

The rejected assignment demonstrates that the first position remains roles rather than an arbitrary string or another route.

The rejected assignment demonstrates that the first position remains roles rather than an arbitrary string or another route. This precision is valuable for configuration and keys, but can be inconvenient for state that must later accept new values.

Do not promise deep immutability or runtime uniqueness from a const parameter. A tuple may contain duplicate names, and referenced nested objects can still be mutable. Validate uniqueness at runtime if the registry requires it, and choose wider state types deliberately where editing is expected.

Rejected counterexample: expected compiler diagnostic

typescript

function names<const T extends readonly string[]>(values: T): T { return values; }
const selected = names(["roles", "news"]);
// @ts-expect-error The first position is specifically roles.
const invalid: "news" = selected[0];
Reasoning
The precise position rejects a different literal instead of widening silently.

Independent practice

Implement a prepend helper for a readonly tuple.

Implement a prepend helper for a readonly tuple. Given a new head and an existing tuple, return a new tuple preserving the head and all remaining positions. Start by manually implementing the concrete ["roles", 2] case before generalizing.

Test an empty tail and a two-item tail, and verify the original tail is unchanged. Optional extension: preserve the head’s literal through a const type parameter. Do not require recursive tuple arithmetic or string puzzles to complete the basic exercise.

Step 1: Spread one head before the tail. Step 2: Give the result a variadic tuple contract.

Practice starter: solve before reading the solution

typescript

function prepend<const H, T extends readonly unknown[]>(head: H, tail: T): [H, ...T] { throw new Error("TODO"); }
Reasoning
Preserve order with a real spread operation and test a zero-length tail.
  • Hint 1: Use a readonly-compatible tail constraint.
  • Hint 2: Already widened arrays cannot regain lost literal positions.

Solution, tests and trade-offs

The solution uses a const head parameter and a readonly variadic tail.

The solution uses a const head parameter and a readonly variadic tail. The return annotation states the positional relationship and the implementation performs a matching spread. This is a justified structural operation rather than a cast that invents values.

Compile receiving assignments for the exact positions, then execute order and nonmutation checks. Try passing an already widened string[] to observe what information has already been lost. The helper cannot reconstruct exact historical literals from a broad array type.

Practice solution and acceptance checks

typescript

function prepend<const H, T extends readonly unknown[]>(head: H, tail: T): [H, ...T] {
  return [head, ...tail];
}
const result: ["roles", 2, true] = prepend("roles", [2, true] as const);

// Acceptance checks
const tail=[2,true] as const;if(result.join(",")!=="roles,2,true"||prepend("a",[] as const).length!==1||tail.length!==2)throw new Error("prepend");
Reasoning
The implementation’s structure matches the variadic tuple return relationship.

Interview reasoning and next step

For interviews, explain what information inference can preserve and what it cannot recover.

For interviews, explain what information inference can preserve and what it cannot recover. Contrast an inline literal, a const-asserted tuple and an already widened variable. Tie variadic tuples to an actual argument-list or sequence operation.

Continue with NoInfer and inferred predicates, which refine where inference comes from and how control-flow evidence is represented. These are tools for clearer contracts, not reasons to make every helper more generic.

Optional stretch — after the core task works: Compare inline literals and a widened variable.

Preserve small literal sequences through reusable helpers without overcomplicating callers.