An excerpt from the repository, fetched Nov 13, 2026.
Read the project’s introduction
JavaScript Records & Tuples Proposal
ImportantThis proposal has been withdrawn #394
Authors:
Robin Ricard (Bloomberg)
Rick Button (Bloomberg)
Nicolò Ribaudo (Babel)
Champions:
Robin Ricard (Bloomberg)
Rick Button (Bloomberg)
Advisors
Philipp Dunkel (Bloomberg)
Dan Ehrenberg (Bloomberg)
Maxwell Heiber
Stage: Withdrawn
Try out Record and Tuple in the playground!
Spec Text
Tutorial
Cookbook
Content
Overview
Examples
Syntax
Equality
The object model
Record and Tuple standard library support
Rationale
Overview
This proposal introduces two new deeply immutable data structures to JavaScript:
Record, a deeply immutable Object-like structure #{ x: 1, y: 2 }
Tuple, a deeply immutable Array-like structure #[1, 2, 3, 4]
Records and Tuples can only contain primitives and other Records and Tuples. You could think of Records and Tuples as "compound primitives". By being thoroughly based on primitives, not objects, Records and Tuples are deeply immutable.
Records and Tuples support comfortable idioms for construction, manipulation and use, similar to working with objects and Arrays. They are compared deeply by their contents, rather than by their identity.
JavaScript engines may perform certain optimizations on construction, manipulation and comparison of Records and Tuples, analogous to the way Strings are often implemented in JS engines. (It should be understood that these optimizations are not guaranteed.)
Records and Tuples aim to be usable and understood with external typesystem supersets such as TypeScript or Flow.
Prior work on immutable data structures in JavaScript
Today, userland libraries implement similar concepts, such as Immutable.js. Also a previous proposal has been attempted but abandoned because of the complexity of the proposal and lack of sufficient use cases.
This new proposal is still inspired by this previous proposal but introduces some significant changes: Record and Tuples are now deeply immutable. This property is fundamentally based on the observation that, in large projects, the risk of mixing immutable and mutable data structures grows as the amount of data being stored and passed around grows as well so you'll be more likely to handle large record & tuple structures. This can introduce hard-to-find bugs.
As a built-in, deeply immutable data structure, this proposal also offers a few usability advantages compared to userland libraries:
Records and Tuples are easily introspectable in a debugger, while library provided immutable types are often hard to inspect as you have to inspect through data structure details.
Because they are accessed through typical object and array idioms, no additional branching is needed in order to write a generic library that consumes both immutable and JS objects; with user libraries, method calls may be needed just in the immutable case.
We avoid cases where developers may expensively convert between regular JS objects and immutable structures, by making it easier to just always use the immutable ones.
Immer is a notable approach to immutable data structures, and prescribes a pattern for manipulation through producers and reducers. However, it is configurable if it generates frozen objects or not. This same pattern can be adapted to the structures defined in this proposal in addition to frozen objects.
Deep equality as defined in user libraries can vary significantly, in part due to possible references to mutable objects. By drawing a hard line about only deeply containing primitives, Records and Tuples, and recursing through the entire structure, this proposal defines simple, unified semantics for comparisons.
Examples
Record
const proposal = #{
id: 1234,
title: "Record & Tuple proposal",
contents: `...`,
// tuples are primitive types so you can put them in records:
keywords: #["ecma", "tc39", "proposal", "record", "tuple"],
};
// Accessing keys like you would with objects!
console.log(proposal.title); // Record & Tuple proposal
console.log(proposal.keywords[1]); // tc39
// Spread like objects!
const proposal2 = #{
...proposal,
title: "Stage 2: Record & Tuple",
};
console.log(proposal2.title); // Stage 2: Record & Tuple
console.log(proposal2.keywords[1]); // tc39
// Object functions work on Records:
console.log(Object.keys(proposal)); // ["contents", "id", "keywords", "title"]
Open in playground
Functions can handle Records and Objects in generally the same way:
const ship1 = #{ x: 1, y: 2 };
// ship2 is an ordinary object:
const ship2 = { x: -1, y: 3 };
function move(start, deltaX, deltaY) {
// we always return a record after moving
return #{
x: start.x + deltaX,
y: start.y + deltaY,
};
}
const ship1Moved = move(ship1, 1, 0);
// passing an ordinary object to move() still works:
const ship2Moved = move(ship2, 3, -1);
console.log(ship1Moved === ship2Moved); // true
// ship1 and ship2 have the same coordinates after moving
Open in playground
See more examples here.
Tuple
const measures = #[42, 12, 67, "measure error: foo happened"];
// Accessing indices like you would with arrays!
console.log(measures[0]); // 42
console.log(measures[3]); // measure error: foo happened
// Slice and spread like arrays!
const correctedMeasures = #[
...measures.slice(0, measures.length - 1),
-1
];
console.log(correctedMeasures[0]); // 42
console.log(correctedMeasures[3]); // -1
// or use the .with() shorthand for the same result:
const correctedMeasures2 = measures.with(3, -1);
console.log(correctedMeasures2[0]); // 42
console.log(correctedMeasures2[3]); // -1
// Tuples support methods similar to Arrays
console.log(correctedMeasures2.map(x => x + 1)); // #[43, 13, 68, 0]
Open in playground
Similarly than with records, we can treat tuples as array-like:
const ship1 = #[1, 2];
// ship2 is an array:
const ship2 = [-1, 3];
function move(start, deltaX, deltaY) {
// we always return a tuple after moving
return #[
start[0] + deltaX,
start[1] + deltaY,
];
}
const ship1Moved = move(ship1, 1, 0);
// p
Read the full README on GitHub