Bring Immutability and Context to Arrays
Const assertions provide immutability but lack semantic meaning. Learn how TypeScript's named tuples and readonly arrays combine to give you both type safety and self-documenting code.
When you write arrays in TypeScript, you face a trade-off. Use as const and you get immutability, but you lose semantic context about what each element means. Use regular types and you get meaningful names, but nothing stops accidental mutations. TypeScript gives you a way to have both: named tuples with the readonly modifier.
Contents
- Starting Simple: Basic Arrays
- Adding Structure: Tuples
- Adding Context: Named Tuples
- The Immutability Problem
- Immutability with Named Tuples
Starting Simple: Basic Arrays
The most straightforward way to type an array in an array is with basic type annotations:
const candles: number[][] = [
[5, 9, 3, 7],
[2, 4, 1, 3],
];This works, but it tells you nothing about what those numbers represent. Is the first number an opening price? A timestamp? Without context, you need to check documentation or remember the structure.
The generic syntax offers the same flexibility with different notation:
const candles: Array<number[]> = [
[5, 9, 3, 7],
[2, 4, 1, 3],
];Both approaches allow any length arrays with any number of elements. That flexibility comes at the cost of type safety.
Adding Structure: Tuples
You can enforce a specific array length using tuple types:
const candles: Array<[number, number, number, number]> = [
[5, 9, 3, 7],
[2, 4, 1, 3],
];Alternative Syntax:
const candles: [number, number, number, number][] = [
[5, 9, 3, 7],
[2, 4, 1, 3],
];Now TypeScript knows each candle must have exactly four numbers. But you still don't know what those numbers represent without external documentation.
Adding Context: Named Tuples
TypeScript allows you to name tuple elements, making their purpose clear:
const candles: Array<[open: number, high: number, low: number, close: number]> = [
[5, 9, 3, 7],
[2, 4, 1, 3],
];The array shorthand syntax works too:
const candles: [open: number, high: number, low: number, close: number][] = [
[5, 9, 3, 7],
[2, 4, 1, 3],
];Now the code documents itself. When you see a candle value, you immediately know which element is the opening price, the high, the low, and the closing price. Your editor's autocomplete and hover information show these names, making the code easier to understand and maintain.
The Immutability Problem
Named tuples solve the context problem, but they don't prevent mutations:
const candles: [open: number, high: number, low: number, close: number][] = [
[5, 9, 3, 7],
[2, 4, 1, 3],
];
candles.push([6, 10, 4, 8]); // This works, but might not be intended
candles[0][0] = 100; // Modifying historical dataYou might reach for as const to prevent modifications:
const candles = [
[5, 9, 3, 7],
[2, 4, 1, 3],
] as const;This creates a deeply immutable structure, but you lose all the semantic information. TypeScript infers literal types like readonly [5, 9, 3, 7], which doesn't help someone reading your code understand what those numbers mean.
Immutability with Named Tuples
TypeScript 3.4 introduced the readonly modifier for arrays and tuples. You can combine it with named tuples to get both immutability and context:
const candles: readonly [open: number, high: number, low: number, close: number][] = [
[5, 9, 3, 7],
[2, 4, 1, 3],
];This prevents adding and removing elements from the array:
candles.push([6, 10, 4, 8]); // Error: Property 'push' does not exist
candles[0][0] = 100; // Still allowed! The inner values are mutableTo make the entire structure deeply immutable, you need to apply readonly to the inner tuples as well. We can do this by using a type alias named OHLC (Open-High-Low-Close):
type OHLC = readonly [open: number, high: number, low: number, close: number];
const candles: readonly OHLC[] = [
[5, 9, 3, 7],
[2, 4, 1, 3],
];Alternative Syntax:
type OHLC = readonly [open: number, high: number, low: number, close: number];
const candles: ReadonlyArray<OHLC> = [
[5, 9, 3, 7],
[2, 4, 1, 3],
];Result:
candles.push([6, 10, 4, 8]); // Error: Property 'push' does not exist
candles[0][0] = 100; // Error: Cannot assign to '0' because it is a read-only propertyNow you have complete immutability with full context.
