Skip to main content

Records and Tuples: JavaScript's New Immutable Data Types

By Craig Buckler

JavaScript

Share:

Free JavaScript Book!

Write powerful, clean and maintainable JavaScript.

RRP $11.95

Records and tuples are new JavaScript immutable data types currently at stage 2 in the TC39 standards approval process. They are subject to change and not currently available in any browser or runtime, but working implementations should arrive within the next year. They help solve a couple of confusing conundrums faced by coders …

Constant Changes

Professional JavaScripters will tell you that assigning variables with const is best practice where possible. It makes variables immutable. Values can’t be changed, so you have fewer issues to deal with.

Unfortunately, const only makes primitive values immutable (String, Number, BigInt, Boolean, Symbol, and undefined). You can’t reassign an array or an object, but the values and properties they contain can be modified. For example:

// array constant
const myArray = [1, 2, 3];

// change array values
myArray[0] = 99;
myArray.push(42);

console.log(myArray); // [ 99, 2, 3, 42 ]

myArray = 'change'; // ERROR!

Similarly for objects:

// object constant
const myObj = { a: 1, b: 2, c: 3 }

// change object properties
myObj.a = 99;
myObj.d = 42;

console.log(myObj); // { a:99 ,b:2, ,c:3, ,d:42 }

myObj = 'change'; // ERROR!

The Object.freeze() method can help, but only shallow freezing is applied to the immediate child properties of an object:

const myObj = { a: 1, b: 2, c: { v: 3 } }
Object.freeze(myObj);

myObj.a = 99; // silently ignored
myObj.c.v = 99; // works fine

console.log(myObj); // { a: 1, b: 2, c: { v: 99 } }

It’s therefore difficult to guarantee a function won’t intentionally or accidentally change the values held in an array or object. Developers must either hope for the best or pass a cloned version of a variable — (which has its own challenges).

Equivalent Inequality

Further chaos can ensue when developers attempt seemingly reasonable object or array comparisons:

const str = 'my string';
console.log( str === 'mystring' );  // true

const num = 123;
console.log( num === 123 );         // true

const arr = [1, 2, 3];
console.log( arr === [1, 2, 3] );   // false

const obj = { a: 1 };
console.log( obj === { a: 1 } );    // false

Only primitive types can be compared by value. Objects and arrays are passed and compared by reference. Two variables will only be equivalent when they point to the same item in memory:

const a = [1, 2];

const b = a;
b.push(3);

console.log( a === b ); // true

// original array has changed
console.log( a ); // [1, 2, 3]

Deeply comparing two objects or arrays requires a recursive comparison function to assess each value in turn. Even then, you may encounter issues with types such as dates or functions which could be stored in different ways.

Tuples: Immutable Array-like Data Structures

Tuples are deeply immutable array-like data structures. They are effectively compound primitive types identified with a # modifier in front of normal array syntax:

// new tuples
const t1 = #[1, 2, 3];
const t2 = #[1, 2, #[3, 4]];

Alternatively, a new Tuple.from() method can create a tuple from an array:

// new tuple from an array
const t3 = Tuple.from( [1, 2, 3] );

Unlike standard arrays, tuples have to satisfy these requirements:

  1. They must not have holes with unset values. For example, #[1,,,4] is invalid.
  2. They must only set primitives, other tuples, or records. Types such as arrays, objects, or functions are not permitted:
  const t4 = #[ new Date() ]; // ERROR (sets an object)
  const t5 = #[1, 2, [3, 4]]; // ERROR (sets an array)

Since tuples are primitives, they can be deeply compared by value with other tuples:

const t6 = #[1, 2];

console.log( t6 === #[1, 2] ); // true

Note that comparisons using the less strict == operator are possible if the tuple holds a single value. For example:

const t7 = #[99];

console.log( t7 == #[99] ); // true
console.log( t7 == 99 );    // true
console.log( t7 == '99' );  // true

// tuple cannot be compared to an array
console.log( t7 == [99] );  // false

Records: Immutable Object-like Data Structures

Records are deeply immutable object-like data structures. Again, they are compound primitive types identified with a # modifier in front of normal object syntax:

// new records
const r1 = #{ a: 1, b: 2 };
const r2 = #{
  a: 1,
  b: #{ c: 2 }, // child record
  d: #[ 3, 4 ]  // child tuple
};

Alternatively, the new Record() constructor can create a record from an object:

// new record from an object
// #{ a: 1, b: 2 }
const r3 = Record({ a: 1, b: 2 });

Or the Record.fromEntries() method can create a record from a series of array or tuple value-pairs:

// new record from array of name-values
// #{ a: 1, b: 2 }
const r4 = Record.fromEntries([
  ['a', 1],
  ['b', 2]
]);

Unlike standard objects, records must fulfill the following requirements:

  1. They must use string property names. For example, #{ Symbol(): 1 } is invalid.
  2. They must only set values using primitives, other tuples, or records. Types such as arrays, objects, or functions are not permitted:
  const r5 = #{ 'd': new Date() };   // ERROR (sets an object)
  const r6 = #{ a: 1, b: { c: 2 } }; // ERROR (sets an object)

Records can be deeply compared with other records and the property order doesn’t matter:

const r7 = #{ a: 1, b: 2 };

console.log( r7 === #{ b: 2, a: 1 } ); // true

Records can only be compared to other records, so using a == or === operator makes no difference. However, it is possible to extract Object keys() and values() for specific comparisons. For example:

const r8 = #{ a: 99 };

console.log( Object.values(r8) == 99 ); // true

Immutable Updates

Tuples and records may sound like complex computer science terms, but they’ll finally permit robust immutable data storage and comparisons in JavaScript. You can try them out today in this playground, or with this polyfill, but please be aware that the proposed implementation could change in the coming months.

Craig is a freelance UK web consultant who built his first page for IE2.0 in 1995. Since that time he's been advocating standards, accessibility, and best-practice HTML5 techniques. He's created enterprise specifications, websites and online applications for companies and organisations including the UK Parliament, the European Parliament, the Department of Energy & Climate Change, Microsoft, and more. He's written more than 1,000 articles for SitePoint and you can find him @craigbuckler.

New books out now!

Get practical advice to start your career in programming!


Master complex transitions, transformations and animations in CSS!

Latest Remote Jobs