Course outline

TypeScript / Basic / Lesson 6

Arrays, tuples, and checked indexing

Model sequences and fixed positional records, account for absent elements, and distinguish readonly views from independent runtime copies.

About 5 min readContent reviewed
On this page

Lesson goals

  • Use arrays, readonly arrays, and tuples for appropriate shapes.
  • Handle indexed access that may produce undefined.
  • Explain shallow copying and safe sequence traversal.

Choose variable length or fixed positions

number[] describes a variable-length array of numbers, while a tuple such as [number, number] gives specific positional information. A tuple is still an ordinary JavaScript array at runtime; its annotation does not validate a deserialized array length. Use a named object when field meanings are clearer than positions. readonly number[] restricts mutation through that view, and a readonly tuple prevents writes through its typed positions. Neither creates a separate allocation or freezes every alias. Choose the model from the operation, including whether empty input is valid and whether positions have distinct meanings.

A valid element type does not promise an element exists

The course enables noUncheckedIndexedAccess, so uncertain array indexing includes undefined. Checking length alone does not always teach the checker a specific arbitrary index is safe; directly test the retrieved value or use an iteration form that supplies existing elements. The first function advertises possible absence in its return type. Its caller chooses a missing-value label with ??, preserving a valid zero. Do not add a non-null assertion merely because test data happened to be nonempty. If nonempty input is a genuine precondition, model and validate it at the construction boundary.

typescript Absence is part of the sequence contract
export {};
function first(values: readonly number[]): number | undefined { return values[0]; }
console.log(first([3, 5]) ?? "missing", first([]) ?? "missing");
Expected output
3 missing

The first array has a value at index zero. The empty array yields undefined and receives the explicit fallback.

Use fixed positions when they have a clear contract

The second example accepts a readonly pair of coordinates and destructures its two known positions. Unlike arbitrary array indexing, those required tuple positions are known to exist in checked callers. The function computes squared distance for small values; it does not claim that every number is finite or that coordinate units are enforced. Returning a scalar avoids exposing internal array mutation. When values come from JSON, validate a two-element numeric array before constructing this domain view. A cast to Point would only tell the checker to trust the caller and would not inspect the runtime length or elements.

typescript Two checked positions and one result
export {};
type Point = readonly [number, number];
function squaredDistance([x, y]: Point): number { return x * x + y * y; }
const point: Point = [3, 4];
console.log(squaredDistance(point));
Expected output
25

The coordinates give nine plus sixteen, or twenty-five. The function does not mutate the readonly input view.

Keep mutation and copying visible

for...of visits array values and is often clearer than maintaining an index when positions are not needed. map creates a new array, while sort mutates its receiver unless code explicitly copies first or uses a supported non-mutating alternative. Array spread is shallow: nested objects remain shared. A const array binding still allows push through a mutable type. Distinguish a readonly parameter from an independent snapshot when work is queued for later. If another owner can mutate data before the callback runs, a readonly annotation on your view does not capture the earlier state.

Test collection shape and resource limits

External arrays need element checks, shape checks, and practical size limits. An array of numbers can contain NaN or infinity, and a huge valid array can exhaust time or memory. Sparse JavaScript arrays add another reason not to infer existence from an element annotation alone. String indexing has text-unit considerations because JavaScript strings use UTF-16 code units. Test empty arrays, missing positions, duplicate values, nested shared objects, and the maximum accepted count. Type-level readonly and tuple information improves local reasoning, while runtime validation establishes whether external bytes satisfy the modeled contract. Keep those responsibilities visible in public documentation.

Common errors and fixes

An indexed value is possibly undefined.

Why it happens
The static array element type does not guarantee that position.
How to fix it
Handle absence or validate a stronger shape.
Prevent it next time
Keep noUncheckedIndexedAccess enabled in checks.

Sorting unexpectedly changes a caller array.

Why it happens
The implementation sorted a shared mutable receiver.
How to fix it
Copy deliberately before mutation when a snapshot is required.
Prevent it next time
State mutation behavior in the function contract.

A copied array still shares nested objects.

Why it happens
Spread created a shallow copy only.
How to fix it
Choose a domain-appropriate independent representation.
Prevent it next time
Test nested aliasing rather than only top-level equality.

Check your understanding

1. Call first with [0]. Explain why the example fallback preserves zero and how || would change the result.

Show worked solution

first returns zero, and ?? preserves it because zero is not null or undefined. || would replace it with missing because zero is falsy. This difference is a domain decision, not a TypeScript compiler optimization. Test zero separately from an empty array to avoid conflating valid data with absence.

2. An unknown JSON value is expected to be a Point. List checks needed before calling squaredDistance and identify a policy still needed after element types pass.

Show worked solution

Check that the value is an array, has exactly two elements, and both entries are numbers. Then enforce finite values and appropriate magnitude or unit rules for the application. Only after those checks should code construct a validated pair. The tuple annotation itself does not inspect input, limit resource usage, or define acceptable geographic or geometric units.

Key takeaways

  • Model absent elements honestly.
  • Readonly access and independent copying differ.
  • Validate external shape before trusting tuple positions.

Official references