Typorithm · easy

2. Inference, literals and widening

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

2. Inference, literals and widening

0%

Concept and contract

Control when values remain literal and when mutable variables need a wider type.

Inference gives a binding a type from its initializer and context. A const string often retains a literal type because reassignment is impossible; a let binding usually widens so later values remain legal. This difference describes permitted future assignments, not runtime immutability.

An object held by const can still have mutable properties. Its status property commonly widens to string, even when initially assigned draft. Decide whether you need a mutable workflow model or an exact configuration value before adding annotations or assertions.

Mechanism and worked example

The example uses a literal union to name the allowed states.

The example uses a literal union to name the allowed states. An explicit annotation gives current a deliberate set of future values. A const assertion preserves configuration literals and marks its properties readonly to checked callers; it does not freeze the JavaScript object.

Contextual typing flows from a receiving signature into an expression. Calling setStatus with an inline literal needs no assertion. An intermediate object may have already widened, so inspect the inferred type at the point of construction rather than forcing the final call to accept it.

Complete worked example

typescript

type Status = "draft" | "published";
let current: Status = "draft";
current = "published";
const defaults = { status: "draft" } as const;
Reasoning
The union permits future state changes; as const preserves only the fixed configuration.

Counterexamples and common mistakes

An unrecognized state is rejected because it is outside the declared union.

An unrecognized state is rejected because it is outside the declared union. This is more useful than a string parameter when the domain has a closed vocabulary. However, accepting user input as that union by assertion merely hides the unknown-input problem.

Do not use as const on every variable mechanically. A readonly tuple may be unsuitable for a function that mutates its input, and a value intended to change should not be modeled as permanently literal. Prefer a precise parameter or variable annotation when mutation is part of the design.

Rejected counterexample: expected compiler diagnostic

typescript

type Status = "draft" | "published";
// @ts-expect-error Unknown vocabulary.
const invalid: Status = "pending";
Reasoning
pending is not a member of Status, regardless of whether JavaScript can store it.

Independent practice

Define a filter sort order with newest and salary as its only accepted values.

Define a filter sort order with newest and salary as its only accepted values. Provide a default configuration whose sort remains the literal newest, while a separate selectedSort variable can change between the two allowed alternatives.

Implement a label function with exhaustive inputs. Check both outputs and reject an unsupported alphabetical sort statically. Explain why const selectedSort = newest and let selectedSort: Sort = newest serve different purposes even when both begin with the same value.

Step 1: Write the two allowed Sort literals. Step 2: Keep defaults fixed and selected state editable.

Practice starter: solve before reading the solution

typescript

type Sort = "newest" | "salary";
function label(sort: Sort): string { throw new Error("TODO"); }
Reasoning
Keep the defaults narrow and the selected value intentionally editable.
  • Hint 1: Use a union annotation for the editable binding.
  • Hint 2: A const assertion belongs on fixed defaults.

Solution, tests and trade-offs

The solution uses a union for editable state and a const assertion for fixed defaults.

The solution uses a union for editable state and a const assertion for fixed defaults. The function accepts the domain vocabulary rather than arbitrary text. This makes refactoring an additional option produce visible work in callers and rendering logic.

Inspect typeof defaults.sort and typeof selectedSort before and after assignments. Runtime checks confirm output spelling; compile-time checks confirm the allowed vocabulary. Neither proves that a saved query parameter belongs to the union, so later boundary lessons will parse that text.

Practice solution and acceptance checks

typescript

type Sort = "newest" | "salary";
const defaults = { sort: "newest" } as const;
let selectedSort: Sort = defaults.sort;
selectedSort = "salary";
function label(sort: Sort): string {
  return sort === "newest" ? "Newest first" : "Highest salary";
}

// Acceptance checks
if(label("newest")!=="Newest first"||label(selectedSort)!=="Highest salary")throw new Error("labels");
Reasoning
Two declarations encode two ownership intentions rather than two different runtime strings.

Interview reasoning and next step

Explain widening using mutability and context, then distinguish readonly checking from Object.freeze.

Explain widening using mutability and context, then distinguish readonly checking from Object.freeze. A useful interview answer includes a const object whose property still changes and a literal value that cannot be assigned to an incompatible union.

Continue with object contracts, where structural compatibility and excess property checks add another layer. Literal inference affects those checks, so keep object construction and assignment separate when investigating a surprising diagnostic.

Optional stretch — after the core task works: Add a third sort mode and update its label deliberately.

Control when values remain literal and when mutable variables need a wider type.