Typorithm · easy

8. Arrays, readonly inputs and positional 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

8. Arrays, readonly inputs and positional tuples

0%

Concept and contract

Choose sequence contracts that preserve ordering, ownership and positional meaning.

An array describes an arbitrary sequence of similarly typed values. A tuple additionally describes positions and, unless it has rest elements, a bounded length. Use named tuple elements to communicate meaning when a small pair is genuinely positional rather than a record with independent named properties.

A readonly array parameter accepts both mutable and readonly arrays without granting the function permission to push, sort in place or overwrite elements. It does not recursively freeze elements. The function must still respect the ownership policy expressed by the type.

Mechanism and worked example

The example returns a new sorted array by copying before sort.

The example returns a new sorted array by copying before sort. The caller retains its original order. The readonly input contract protects the implementation from accidental in-place mutation while allowing callers to pass a frozen-looking literal tuple or an ordinary mutable array.

A readonly pair has known first and second elements, unlike a general array. Under noUncheckedIndexedAccess, an arbitrary array index can be undefined. Tuple positions that are required and in range remain definite, but an optional tuple element still introduces uncertainty.

Complete worked example

typescript

function sorted(values: readonly number[]): number[] {
  return [...values].sort((a, b) => a - b);
}
const original = [3, 1, 2];
const ordered = sorted(original);
Reasoning
Copying protects caller order while the readonly parameter prevents accidental mutating methods.

Counterexamples and common mistakes

The rejected push illustrates a capability boundary: the reference does not grant mutation.

The rejected push illustrates a capability boundary: the reference does not grant mutation. Casting it back to a mutable array would evade the promised contract. Sorting a caller-owned array can create subtle UI bugs even when the returned values look correct in one isolated test.

Avoid using a tuple to label a large domain object with many optional positions. An object with named properties is clearer for an evolving API. Likewise, spreading a tuple into an array can lose its positional information unless an appropriate tuple context preserves it.

Rejected counterexample: expected compiler diagnostic

typescript

const values: readonly number[] = [1, 2];
// @ts-expect-error Mutation is not granted by this reference.
values.push(3);
Reasoning
The runtime object may be mutable, but this reference does not expose push.

Independent practice

Implement normalizeRange for a readonly pair of numbers.

Implement normalizeRange for a readonly pair of numbers. Return the smaller number first without modifying the input. Preserve equal endpoints and negative numbers. Then explain why a general number[] is insufficient when both positions are required.

Use a readonly tuple as one test input and a mutable pair as another. Reject a one-element tuple at compile time. The task concerns ordering and shape, so reject nonfinite numbers only if that is part of your explicitly documented domain policy.

Step 1: Destructure the required pair. Step 2: Return a new pair in ascending order.

Practice starter: solve before reading the solution

typescript

function normalizeRange(pair: readonly [number, number]): [number, number] { throw new Error("TODO"); }
Reasoning
Keep the two required positions explicit and return a new pair.
  • Hint 1: A tuple guarantees two required positions.
  • Hint 2: Copying preserves the caller’s order.

Solution, tests and trade-offs

The solution compares the two known endpoints and constructs a new pair.

The solution compares the two known endpoints and constructs a new pair. Tuple destructuring is safe here because both positions are required. The return annotation ensures the result remains a pair rather than widening to an arbitrary number array.

Check reversed, already ordered and equal inputs, and verify the original reversed pair remains unchanged. A type test for a missing endpoint complements the runtime order tests. Do not claim readonly guarantees deep immutability or that ordering validation covers every possible numeric business rule.

Practice solution and acceptance checks

typescript

function normalizeRange(pair: readonly [number, number]): [number, number] {
  const [a, b] = pair;
  return a <= b ? [a, b] : [b, a];
}

// Acceptance checks
const x:[number,number]=[4,-2];if(normalizeRange(x).join(",")!=="-2,4"||x[0]!==4||normalizeRange([1,1]).join(",")!=="1,1")throw new Error("range");
Reasoning
The tuple preserves required positions; the comparison establishes ordered output.

Interview reasoning and next step

Explain how readonly changes assignability and available operations rather than JavaScript object identity.

Explain how readonly changes assignability and available operations rather than JavaScript object identity. Compare readonly number[], readonly [number, number] and an object with min and max. Choose based on the consumer’s actual contract rather than syntactic brevity.

The next lesson introduces generics, which let a collection operation preserve its element type without duplicating implementations. Retain readonly inputs there so type reuse does not accidentally broaden mutation permissions.

Optional stretch — after the core task works: Add finite-number validation with an explicit error policy.

Choose sequence contracts that preserve ordering, ownership and positional meaning.