Was this page helpful?

Object Types

In JavaScript, the fundamental way that we group and pass around data is through objects.In TypeScript, we represent those throughobject types.

As we’ve seen, they can be anonymous:

ts

or they can be named by using either an interface:

ts
interfacePerson {
name:string;
age:number;
}
 
functiongreet(person:Person) {
return"Hello " +person.name;
}
Try

or a type alias:

ts
typePerson = {
name:string;
age:number;
};
 
functiongreet(person:Person) {
return"Hello " +person.name;
}
Try

In all three examples above, we’ve written functions that take objects that contain the propertyname (which must be astring) andage (which must be anumber).

Quick Reference

We have cheat-sheets available for bothtype andinterface, if you want a quick look at the important every-day syntax at a glance.

Property Modifiers

Each property in an object type can specify a couple of things: the type, whether the property is optional, and whether the property can be written to.

Optional Properties

Much of the time, we’ll find ourselves dealing with objects thatmight have a property set.In those cases, we can mark those properties asoptional by adding a question mark (?) to the end of their names.

ts
interfacePaintOptions {
shape:Shape;
xPos?:number;
yPos?:number;
}
 
functionpaintShape(opts:PaintOptions) {
// ...
}
 
constshape =getShape();
paintShape({shape });
paintShape({shape,xPos:100 });
paintShape({shape,yPos:100 });
paintShape({shape,xPos:100,yPos:100 });
Try

In this example, bothxPos andyPos are considered optional.We can choose to provide either of them, so every call above topaintShape is valid.All optionality really says is that if the propertyis set, it better have a specific type.

We can also read from those properties - but when we do understrictNullChecks, TypeScript will tell us they’re potentiallyundefined.

ts
functionpaintShape(opts:PaintOptions) {
letxPos =opts.xPos;
(property) PaintOptions.xPos?: number | undefined
letyPos =opts.yPos;
(property) PaintOptions.yPos?: number | undefined
// ...
}
Try

In JavaScript, even if the property has never been set, we can still access it - it’s just going to give us the valueundefined.We can just handleundefined specially by checking for it.

ts
functionpaintShape(opts:PaintOptions) {
letxPos =opts.xPos ===undefined ?0 :opts.xPos;
let xPos: number
letyPos =opts.yPos ===undefined ?0 :opts.yPos;
let yPos: number
// ...
}
Try

Note that this pattern of setting defaults for unspecified values is so common that JavaScript has syntax to support it.

ts
functionpaintShape({shape,xPos =0,yPos =0 }:PaintOptions) {
console.log("x coordinate at",xPos);
(parameter) xPos: number
console.log("y coordinate at",yPos);
(parameter) yPos: number
// ...
}
Try

Here we useda destructuring pattern forpaintShape’s parameter, and provideddefault values forxPos andyPos.NowxPos andyPos are both definitely present within the body ofpaintShape, but optional for any callers topaintShape.

Note that there is currently no way to place type annotations within destructuring patterns.This is because the following syntax already means something different in JavaScript.

ts
functiondraw({shape:Shape,xPos:number =100/*...*/ }) {
render(shape);
Cannot find name 'shape'. Did you mean 'Shape'?2552Cannot find name 'shape'. Did you mean 'Shape'?
render(xPos);
Cannot find name 'xPos'.2304Cannot find name 'xPos'.
}
Try

In an object destructuring pattern,shape: Shape means “grab the propertyshape and redefine it locally as a variable namedShape.”LikewisexPos: number creates a variable namednumber whose value is based on the parameter’sxPos.

readonly Properties

Properties can also be marked asreadonly for TypeScript.While it won’t change any behavior at runtime, a property marked asreadonly can’t be written to during type-checking.

ts
interfaceSomeType {
readonlyprop:string;
}
 
functiondoSomething(obj:SomeType) {
// We can read from 'obj.prop'.
console.log(`prop has the value '${obj.prop}'.`);
 
// But we can't re-assign it.
obj.prop ="hello";
Cannot assign to 'prop' because it is a read-only property.2540Cannot assign to 'prop' because it is a read-only property.
}
Try

Using thereadonly modifier doesn’t necessarily imply that a value is totally immutable - or in other words, that its internal contents can’t be changed.It just means the property itself can’t be re-written to.

ts
interfaceHome {
readonlyresident: {name:string;age:number };
}
 
functionvisitForBirthday(home:Home) {
// We can read and update properties from 'home.resident'.
console.log(`Happy birthday${home.resident.name}!`);
home.resident.age++;
}
 
functionevict(home:Home) {
// But we can't write to the 'resident' property itself on a 'Home'.
home.resident = {
Cannot assign to 'resident' because it is a read-only property.2540Cannot assign to 'resident' because it is a read-only property.
name:"Victor the Evictor",
age:42,
};
}
Try

It’s important to manage expectations of whatreadonly implies.It’s useful to signal intent during development time for TypeScript on how an object should be used.TypeScript doesn’t factor in whether properties on two types arereadonly when checking whether those types are compatible, soreadonly properties can also change via aliasing.

ts
interfacePerson {
name:string;
age:number;
}
 
interfaceReadonlyPerson {
readonlyname:string;
readonlyage:number;
}
 
letwritablePerson:Person = {
name:"Person McPersonface",
age:42,
};
 
// works
letreadonlyPerson:ReadonlyPerson =writablePerson;
 
console.log(readonlyPerson.age);// prints '42'
writablePerson.age++;
console.log(readonlyPerson.age);// prints '43'
Try

Usingmapping modifiers, you can removereadonly attributes.

Index Signatures

Sometimes you don’t know all the names of a type’s properties ahead of time, but you do know the shape of the values.

In those cases you can use an index signature to describe the types of possible values, for example:

ts
interfaceStringArray {
[index:number]:string;
}
 
constmyArray:StringArray =getStringArray();
constsecondItem =myArray[1];
const secondItem: string
Try

Above, we have aStringArray interface which has an index signature.This index signature states that when aStringArray is indexed with anumber, it will return astring.

Only some types are allowed for index signature properties:string,number,symbol, template string patterns, and union types consisting only of these.

It is possible to support multiple types of indexers...

It is possible to support multiple types of indexers. Note that when using both `number` and `string` indexers, the type returned from a numeric indexer must be a subtype of the type returned from the string indexer. This is because when indexing with anumber, JavaScript will actually convert that to astring before indexing into an object. That means that indexing with100 (anumber) is the same thing as indexing with"100" (astring), so the two need to be consistent.

ts
interfaceAnimal {
name:string;
}
 
interfaceDogextendsAnimal {
breed:string;
}
 
// Error: indexing with a numeric string might get you a completely separate type of Animal!
interfaceNotOkay {
[x:number]:Animal;
'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.2413'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
[x:string]:Dog;
}
Try

While string index signatures are a powerful way to describe the “dictionary” pattern, they also enforce that all properties match their return type.This is because a string index declares thatobj.property is also available asobj["property"].In the following example,name’s type does not match the string index’s type, and the type checker gives an error:

ts
interfaceNumberDictionary {
[index:string]:number;
 
length:number;// ok
name:string;
Property 'name' of type 'string' is not assignable to 'string' index type 'number'.2411Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}
Try

However, properties of different types are acceptable if the index signature is a union of the property types:

ts
interfaceNumberOrStringDictionary {
[index:string]:number |string;
length:number;// ok, length is a number
name:string;// ok, name is a string
}
Try

Finally, you can make index signaturesreadonly in order to prevent assignment to their indices:

ts
interfaceReadonlyStringArray {
readonly [index:number]:string;
}
 
letmyArray:ReadonlyStringArray =getReadOnlyStringArray();
myArray[2] ="Mallory";
Index signature in type 'ReadonlyStringArray' only permits reading.2542Index signature in type 'ReadonlyStringArray' only permits reading.
Try

You can’t setmyArray[2] because the index signature isreadonly.

Excess Property Checks

Where and how an object is assigned a type can make a difference in the type system.One of the key examples of this is in excess property checking, which validates the object more thoroughly when it is created and assigned to an object type during creation.

ts
interfaceSquareConfig {
color?:string;
width?:number;
}
 
functioncreateSquare(config:SquareConfig): {color:string;area:number } {
return {
color:config.color ||"red",
area:config.width ?config.width *config.width :20,
};
}
 
letmySquare =createSquare({colour:"red",width:100 });
Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?2561Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?
Try

Notice the given argument tocreateSquare is spelledcolour instead ofcolor.In plain JavaScript, this sort of thing fails silently.

You could argue that this program is correctly typed, since thewidth properties are compatible, there’s nocolor property present, and the extracolour property is insignificant.

However, TypeScript takes the stance that there’s probably a bug in this code.Object literals get special treatment and undergoexcess property checking when assigning them to other variables, or passing them as arguments.If an object literal has any properties that the “target type” doesn’t have, you’ll get an error:

ts
letmySquare =createSquare({colour:"red",width:100 });
Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?2561Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?
Try

Getting around these checks is actually really simple.The easiest method is to just use a type assertion:

ts
letmySquare =createSquare({width:100,opacity:0.5 }asSquareConfig);
Try

However, a better approach might be to add a string index signature if you’re sure that the object can have some extra properties that are used in some special way.IfSquareConfig can havecolor andwidth properties with the above types, but couldalso have any number of other properties, then we could define it like so:

ts
interfaceSquareConfig {
color?:string;
width?:number;
[propName:string]:unknown;
}
Try

Here we’re saying thatSquareConfig can have any number of properties, and as long as they aren’tcolor orwidth, their types don’t matter.

One final way to get around these checks, which might be a bit surprising, is to assign the object to another variable:Since assigningsquareOptions won’t undergo excess property checks, the compiler won’t give you an error:

ts
letsquareOptions = {colour:"red",width:100 };
letmySquare =createSquare(squareOptions);
Try

The above workaround will work as long as you have a common property betweensquareOptions andSquareConfig.In this example, it was the propertywidth. It will however, fail if the variable does not have any common object property. For example:

ts
letsquareOptions = {colour:"red" };
letmySquare =createSquare(squareOptions);
Type '{ colour: string; }' has no properties in common with type 'SquareConfig'.2559Type '{ colour: string; }' has no properties in common with type 'SquareConfig'.
Try

Keep in mind that for simple code like above, you probably shouldn’t be trying to “get around” these checks.For more complex object literals that have methods and hold state, you might need to keep these techniques in mind, but a majority of excess property errors are actually bugs.

That means if you’re running into excess property checking problems for something like option bags, you might need to revise some of your type declarations.In this instance, if it’s okay to pass an object with both acolor orcolour property tocreateSquare, you should fix up the definition ofSquareConfig to reflect that.

Extending Types

It’s pretty common to have types that might be more specific versions of other types.For example, we might have aBasicAddress type that describes the fields necessary for sending letters and packages in the U.S.

ts
interfaceBasicAddress {
name?:string;
street:string;
city:string;
country:string;
postalCode:string;
}
Try

In some situations that’s enough, but addresses often have a unit number associated with them if the building at an address has multiple units.We can then describe anAddressWithUnit.

ts
interfaceAddressWithUnit {
name?:string;
unit:string;
street:string;
city:string;
country:string;
postalCode:string;
}
Try

This does the job, but the downside here is that we had to repeat all the other fields fromBasicAddress when our changes were purely additive.Instead, we can extend the originalBasicAddress type and just add the new fields that are unique toAddressWithUnit.

ts
interfaceBasicAddress {
name?:string;
street:string;
city:string;
country:string;
postalCode:string;
}
 
interfaceAddressWithUnitextendsBasicAddress {
unit:string;
}
Try

Theextends keyword on aninterface allows us to effectively copy members from other named types, and add whatever new members we want.This can be useful for cutting down the amount of type declaration boilerplate we have to write, and for signaling intent that several different declarations of the same property might be related.For example,AddressWithUnit didn’t need to repeat thestreet property, and becausestreet originates fromBasicAddress, a reader will know that those two types are related in some way.

interfaces can also extend from multiple types.

ts
interfaceColorful {
color:string;
}
 
interfaceCircle {
radius:number;
}
 
interfaceColorfulCircleextendsColorful,Circle {}
 
constcc:ColorfulCircle = {
color:"red",
radius:42,
};
Try

Intersection Types

interfaces allowed us to build up new types from other types by extending them.TypeScript provides another construct calledintersection types that is mainly used to combine existing object types.

An intersection type is defined using the& operator.

ts
interfaceColorful {
color:string;
}
interfaceCircle {
radius:number;
}
 
typeColorfulCircle =Colorful &Circle;
Try

Here, we’ve intersectedColorful andCircle to produce a new type that has all the members ofColorfulandCircle.

ts
functiondraw(circle:Colorful &Circle) {
console.log(`Color was${circle.color}`);
console.log(`Radius was${circle.radius}`);
}
 
// okay
draw({color:"blue",radius:42 });
 
// oops
draw({color:"red",raidus:42 });
Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?2561Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?
Try

Interface Extension vs. Intersection

We just looked at two ways to combine types which are similar, but are actually subtly different.With interfaces, we could use anextends clause to extend from other types, and we were able to do something similar with intersections and name the result with a type alias.The principal difference between the two is how conflicts are handled, and that difference is typically one of the main reasons why you’d pick one over the other between an interface and a type alias of an intersection type.

If interfaces are defined with the same name, TypeScript will attempt to merge them if the properties are compatible. If the properties are not compatible (i.e., they have the same property name but different types), TypeScript will raise an error.

In the case of intersection types, properties with different types will be merged automatically. When the type is used later, TypeScript will expect the property to satisfy both types simultaneously, which may produce unexpected results.

For example, the following code will throw an error because the properties are incompatible:

ts
interfacePerson {
name:string;
}
interfacePerson {
name:number;
}

In contrast, the following code will compile, but it results in anever type:

ts
interfacePerson1 {
name:string;
}
 
interfacePerson2 {
name:number;
}
 
typeStaff =Person1 &Person2
 
declareconststaffer:Staff;
staffer.name;
(property) name: never
Try

In this case, Staff would require the name property to be both a string and a number, which results in property being of typenever.

Generic Object Types

Let’s imagine aBox type that can contain any value -strings,numbers,Giraffes, whatever.

ts
interfaceBox {
contents:any;
}
Try

Right now, thecontents property is typed asany, which works, but can lead to accidents down the line.

We could instead useunknown, but that would mean that in cases where we already know the type ofcontents, we’d need to do precautionary checks, or use error-prone type assertions.

ts
interfaceBox {
contents:unknown;
}
 
letx:Box = {
contents:"hello world",
};
 
// we could check 'x.contents'
if (typeofx.contents ==="string") {
console.log(x.contents.toLowerCase());
}
 
// or we could use a type assertion
console.log((x.contentsasstring).toLowerCase());
Try

One type safe approach would be to instead scaffold out differentBox types for every type ofcontents.

ts
interfaceNumberBox {
contents:number;
}
 
interfaceStringBox {
contents:string;
}
 
interfaceBooleanBox {
contents:boolean;
}
Try

But that means we’ll have to create different functions, or overloads of functions, to operate on these types.

ts
functionsetContents(box:StringBox,newContents:string):void;
functionsetContents(box:NumberBox,newContents:number):void;
functionsetContents(box:BooleanBox,newContents:boolean):void;
functionsetContents(box: {contents:any },newContents:any) {
box.contents =newContents;
}
Try

That’s a lot of boilerplate. Moreover, we might later need to introduce new types and overloads.This is frustrating, since our box types and overloads are all effectively the same.

Instead, we can make agenericBox type which declares atype parameter.

ts
interfaceBox<Type> {
contents:Type;
}
Try

You might read this as “ABox ofType is something whosecontents have typeType”.Later on, when we refer toBox, we have to give atype argument in place ofType.

ts
letbox:Box<string>;
Try

Think ofBox as a template for a real type, whereType is a placeholder that will get replaced with some other type.When TypeScript seesBox<string>, it will replace every instance ofType inBox<Type> withstring, and end up working with something like{ contents: string }.In other words,Box<string> and our earlierStringBox work identically.

ts
interfaceBox<Type> {
contents:Type;
}
interfaceStringBox {
contents:string;
}
 
letboxA:Box<string> = {contents:"hello" };
boxA.contents;
(property) Box<string>.contents: string
 
letboxB:StringBox = {contents:"world" };
boxB.contents;
(property) StringBox.contents: string
Try

Box is reusable in thatType can be substituted with anything. That means that when we need a box for a new type, we don’t need to declare a newBox type at all (though we certainly could if we wanted to).

ts
interfaceBox<Type> {
contents:Type;
}
 
interfaceApple {
// ....
}
 
// Same as '{ contents: Apple }'.
typeAppleBox =Box<Apple>;
Try

This also means that we can avoid overloads entirely by instead usinggeneric functions.

ts
functionsetContents<Type>(box:Box<Type>,newContents:Type) {
box.contents =newContents;
}
Try

It is worth noting that type aliases can also be generic. We could have defined our newBox<Type> interface, which was:

ts
interfaceBox<Type> {
contents:Type;
}
Try

by using a type alias instead:

ts
typeBox<Type> = {
contents:Type;
};
Try

Since type aliases, unlike interfaces, can describe more than just object types, we can also use them to write other kinds of generic helper types.

ts
typeOrNull<Type> =Type |null;
 
typeOneOrMany<Type> =Type |Type[];
 
typeOneOrManyOrNull<Type> =OrNull<OneOrMany<Type>>;
type OneOrManyOrNull<Type> = OneOrMany<Type> | null
 
typeOneOrManyOrNullStrings =OneOrManyOrNull<string>;
type OneOrManyOrNullStrings = OneOrMany<string> | null
Try

We’ll circle back to type aliases in just a little bit.

TheArray Type

Generic object types are often some sort of container type that work independently of the type of elements they contain.It’s ideal for data structures to work this way so that they’re re-usable across different data types.

It turns out we’ve been working with a type just like that throughout this handbook: theArray type.Whenever we write out types likenumber[] orstring[], that’s really just a shorthand forArray<number> andArray<string>.

ts
functiondoSomething(value:Array<string>) {
// ...
}
 
letmyArray:string[] = ["hello","world"];
 
// either of these work!
doSomething(myArray);
doSomething(newArray("hello","world"));
Try

Much like theBox type above,Array itself is a generic type.

ts
interfaceArray<Type> {
/**
* Gets or sets the length of the array.
*/
length:number;
 
/**
* Removes the last element from an array and returns it.
*/
pop():Type |undefined;
 
/**
* Appends new elements to an array, and returns the new length of the array.
*/
push(...items:Type[]):number;
 
// ...
}
Try

Modern JavaScript also provides other data structures which are generic, likeMap<K, V>,Set<T>, andPromise<T>.All this really means is that because of howMap,Set, andPromise behave, they can work with any sets of types.

TheReadonlyArray Type

TheReadonlyArray is a special type that describes arrays that shouldn’t be changed.

ts
functiondoStuff(values:ReadonlyArray<string>) {
// We can read from 'values'...
constcopy =values.slice();
console.log(`The first value is${values[0]}`);
 
// ...but we can't mutate 'values'.
values.push("hello!");
Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.
}
Try

Much like thereadonly modifier for properties, it’s mainly a tool we can use for intent.When we see a function that returnsReadonlyArrays, it tells us we’re not meant to change the contents at all, and when we see a function that consumesReadonlyArrays, it tells us that we can pass any array into that function without worrying that it will change its contents.

UnlikeArray, there isn’t aReadonlyArray constructor that we can use.

ts
newReadonlyArray("red","green","blue");
'ReadonlyArray' only refers to a type, but is being used as a value here.2693'ReadonlyArray' only refers to a type, but is being used as a value here.
Try

Instead, we can assign regularArrays toReadonlyArrays.

ts
constroArray:ReadonlyArray<string> = ["red","green","blue"];
Try

Just as TypeScript provides a shorthand syntax forArray<Type> withType[], it also provides a shorthand syntax forReadonlyArray<Type> withreadonly Type[].

ts
functiondoStuff(values:readonlystring[]) {
// We can read from 'values'...
constcopy =values.slice();
console.log(`The first value is${values[0]}`);
 
// ...but we can't mutate 'values'.
values.push("hello!");
Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.
}
Try

One last thing to note is that unlike thereadonly property modifier, assignability isn’t bidirectional between regularArrays andReadonlyArrays.

ts
letx:readonlystring[] = [];
lety:string[] = [];
 
x =y;
y =x;
The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.4104The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.
Try

Tuple Types

Atuple type is another sort ofArray type that knows exactly how many elements it contains, and exactly which types it contains at specific positions.

ts
typeStringNumberPair = [string,number];
Try

Here,StringNumberPair is a tuple type ofstring andnumber.LikeReadonlyArray, it has no representation at runtime, but is significant to TypeScript.To the type system,StringNumberPair describes arrays whose0 index contains astring and whose1 index contains anumber.

ts
functiondoSomething(pair: [string,number]) {
consta =pair[0];
const a: string
constb =pair[1];
const b: number
// ...
}
 
doSomething(["hello",42]);
Try

If we try to index past the number of elements, we’ll get an error.

ts
functiondoSomething(pair: [string,number]) {
// ...
 
constc =pair[2];
Tuple type '[string, number]' of length '2' has no element at index '2'.2493Tuple type '[string, number]' of length '2' has no element at index '2'.
}
Try

We can alsodestructure tuples using JavaScript’s array destructuring.

ts
functiondoSomething(stringHash: [string,number]) {
const [inputString,hash] =stringHash;
 
console.log(inputString);
const inputString: string
 
console.log(hash);
const hash: number
}
Try

Tuple types are useful in heavily convention-based APIs, where each element’s meaning is “obvious”.This gives us flexibility in whatever we want to name our variables when we destructure them.In the above example, we were able to name elements0 and1 to whatever we wanted.

However, since not every user holds the same view of what’s obvious, it may be worth reconsidering whether using objects with descriptive property names may be better for your API.

Other than those length checks, simple tuple types like these are equivalent to types which are versions ofArrays that declare properties for specific indexes, and that declarelength with a numeric literal type.

ts
interfaceStringNumberPair {
// specialized properties
length:2;
0:string;
1:number;
 
// Other 'Array<string | number>' members...
slice(start?:number,end?:number):Array<string |number>;
}
Try

Another thing you may be interested in is that tuples can have optional properties by writing out a question mark (? after an element’s type).Optional tuple elements can only come at the end, and also affect the type oflength.

ts
typeEither2dOr3d = [number,number,number?];
 
functionsetCoordinate(coord:Either2dOr3d) {
const [x,y,z] =coord;
const z: number | undefined
 
console.log(`Provided coordinates had${coord.length} dimensions`);
(property) length: 2 | 3
}
Try

Tuples can also have rest elements, which have to be an array/tuple type.

ts
typeStringNumberBooleans = [string,number, ...boolean[]];
typeStringBooleansNumber = [string, ...boolean[],number];
typeBooleansStringNumber = [...boolean[],string,number];
Try
  • StringNumberBooleans describes a tuple whose first two elements arestring andnumber respectively, but which may have any number ofbooleans following.
  • StringBooleansNumber describes a tuple whose first element isstring and then any number ofbooleans and ending with anumber.
  • BooleansStringNumber describes a tuple whose starting elements are any number ofbooleans and ending with astring then anumber.

A tuple with a rest element has no set “length” - it only has a set of well-known elements in different positions.

ts
consta:StringNumberBooleans = ["hello",1];
constb:StringNumberBooleans = ["beautiful",2,true];
constc:StringNumberBooleans = ["world",3,true,false,true,false,true];
Try

Why might optional and rest elements be useful?Well, it allows TypeScript to correspond tuples with parameter lists.Tuples types can be used inrest parameters and arguments, so that the following:

ts
functionreadButtonInput(...args: [string,number, ...boolean[]]) {
const [name,version, ...input] =args;
// ...
}
Try

is basically equivalent to:

ts
functionreadButtonInput(name:string,version:number, ...input:boolean[]) {
// ...
}
Try

This is handy when you want to take a variable number of arguments with a rest parameter, and you need a minimum number of elements, but you don’t want to introduce intermediate variables.

readonly Tuple Types

One final note about tuple types - tuple types havereadonly variants, and can be specified by sticking areadonly modifier in front of them - just like with array shorthand syntax.

ts
functiondoSomething(pair:readonly [string,number]) {
// ...
}
Try

As you might expect, writing to any property of areadonly tuple isn’t allowed in TypeScript.

ts
functiondoSomething(pair:readonly [string,number]) {
pair[0] ="hello!";
Cannot assign to '0' because it is a read-only property.2540Cannot assign to '0' because it is a read-only property.
}
Try

Tuples tend to be created and left un-modified in most code, so annotating types asreadonly tuples when possible is a good default.This is also important given that array literals withconst assertions will be inferred withreadonly tuple types.

ts
letpoint = [3,4]asconst;
 
functiondistanceFromOrigin([x,y]: [number,number]) {
returnMath.sqrt(x **2 +y **2);
}
 
distanceFromOrigin(point);
Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'. The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.2345Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'. The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.
Try

Here,distanceFromOrigin never modifies its elements, but expects a mutable tuple.Sincepoint’s type was inferred asreadonly [3, 4], it won’t be compatible with[number, number] since that type can’t guaranteepoint’s elements won’t be mutated.

The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request

Contributors to this page:
DRDaniel Rosenwasser  (52)
OTOrta Therox  (16)
338elements  (2)
BRBruce Robertson  (2)
ARAlan Rempel  (2)
23+

Last updated: Jul 14, 2025