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.
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.
export {};
function first(values: readonly number[]): number | undefined { return values[0]; }
console.log(first([3, 5]) ?? "missing", first([]) ?? "missing");
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.
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));
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.