HOWTO · JavaScript
JavaScript Tuple Alternatives: Arrays, Objects, and TypeScript
JavaScript has no native tuple type. Use arrays, objects, Object.freeze(), destructuring, or TypeScript tuples for the appropriate contract.
On this page
JavaScript has no native tuple value at runtime. For a short, ordered group such as coordinates, use an array and destructure it. Use an object when names communicate the data better. If the code must reject direct runtime changes, freeze the array; if it needs a fixed length and element types during development, use a TypeScript tuple.
Use an Array for an Ordered Pair
An array is a good tuple-like value when every position has a stable meaning. For example, this function returns a minimum followed by a maximum:
function minMax(values) {
return [Math.min(...values), Math.max(...values)];
}
const [minimum, maximum] = minMax([7, 3, 12]);
console.log(minimum, maximum);
Output:
3 12
Destructuring gives the positions useful local names. Document the order for callers. JavaScript does not enforce either an array length or element types, so validate external data before relying on a positional contract.
Freeze an Array Only When Runtime Immutability Matters
const prevents rebinding a variable; it does not prevent changing an array held by that variable. Object.freeze() freezes the array itself:
"use strict";
const point = Object.freeze([48.8566, 2.3522]);
console.log(point[0], point[1], Object.isFrozen(point));
// point[0] = 0; // TypeError in strict mode
// point.push(3); // TypeError
Output:
48.8566 2.3522 true
Object.freeze() returns the same object, rather than a copy. If the caller owns an input array, copy it before freezing so a helper does not unexpectedly constrain the caller:
function asFrozenPoint(value) {
if (
!Array.isArray(value) ||
value.length !== 2 ||
!value.every(Number.isFinite)
) {
throw new TypeError("Expected two finite coordinates");
}
return Object.freeze([...value]);
}
Freezing is shallow. It prevents changing direct elements, the array length, and direct properties, but objects inside the array are still mutable:
const settings = Object.freeze([{ theme: "light" }]);
settings[0].theme = "dark";
console.log(settings[0].theme);
Output:
dark
Do not call a shallow-frozen nested structure deeply immutable. A deep-freeze utility has to handle nested values and cycles, so use one only when that is a genuine requirement.
Use an Object When Names Matter More Than Position
Positional values become hard to read when they contain several strings, booleans, optional fields, or fields likely to change. Return an object instead:
function createUser() {
return { id: 42, name: "Ada", active: true };
}
const { id, name, active } = createUser();
An array is appropriate for compact, established pairs such as [x, y] or the [key, value] entries returned by Object.entries(). An object is safer when callers would otherwise need to remember what index 1 or 2 means.
For example, Object.entries() already provides a documented positional pair, so destructuring makes the two positions explicit at the use site:
for (const [key, value] of Object.entries({ theme: "light" })) {
console.log(`${key}: ${value}`);
}
Output:
theme: light
Use TypeScript for a Checked Tuple Contract
TypeScript can express the length, order, and element types that JavaScript cannot enforce:
const point: readonly [number, number] = [48.8566, 2.3522];
readonly prevents writes in TypeScript source. After compilation, this remains an ordinary JavaScript array; it does not freeze the value at runtime. Combine a TypeScript tuple with Object.freeze() only when both compile-time checking and runtime protection are needed. See the TypeScript tuple documentation for the static type rules.
Summary
JavaScript arrays can act like tuples by convention, especially with destructuring. Choose an object when property names are clearer, Object.freeze() for shallow runtime protection, and TypeScript tuples for static length and type checks.