In the following TypeScript code:
type User = [number, string];
const newUser: User = [112, "[email protected]"];
newUser[1] = "hc.com"; // ✅ Allowed
newUser.push(true); // ⚠️ No error?!
I expected TypeScript to prevent newUser.push(true) since User is defined as a tuple of [number, string]. However, TypeScript allows this due to the mutable nature of tuples.
Tuples in TypeScript are essentially special arrays. At runtime, there's no real distinction between an array and a tuple — both are JavaScript arrays. Unless specified otherwise, tuples are mutable, and methods like .push() are available.
So newUser.push(true) compiles because:
TypeScript treats the tuple as an array.
.push() exists on arrays.
TypeScript doesn't strictly enforce the tuple's length or element types for mutations unless stricter typing is applied.
type User = readonly [number, string];
const newUser = [112, "[email protected]"] as const;
This will infer the type as readonly [112, "[email protected]"] and block any mutation attempts.