This page has been deprecated
This handbook page has been replaced,go to the new page
Interfaces
One of TypeScript’s core principles is that type checking focuses on theshape that values have.This is sometimes called “duck typing” or “structural subtyping”.In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project.
Our First Interface
The easiest way to see how interfaces work is to start with a simple example:
tsTry
functionprintLabel (labeledObj : {label :string }) {console .log (labeledObj .label );}letmyObj = {size :10,label :"Size 10 Object" };printLabel (myObj );
The type checker checks the call toprintLabel
.TheprintLabel
function has a single parameter that requires that the object passed in has a property calledlabel
of typestring
.Notice that our object actually has more properties than this, but the compiler only checks thatat least the ones required are present and match the types required.There are some cases where TypeScript isn’t as lenient, which we’ll cover in a bit.
We can write the same example again, this time using an interface to describe the requirement of having thelabel
property that is a string:
tsTry
interfaceLabeledValue {label :string;}functionprintLabel (labeledObj :LabeledValue ) {console .log (labeledObj .label );}letmyObj = {size :10,label :"Size 10 Object" };printLabel (myObj );
The interfaceLabeledValue
is a name we can now use to describe the requirement in the previous example.It still represents having a single property calledlabel
that is of typestring
.Notice we didn’t have to explicitly say that the object we pass toprintLabel
implements this interface like we might have to in other languages.Here, it’s only the shape that matters. If the object we pass to the function meets the requirements listed, then it’s allowed.
It’s worth pointing out that the type checker does not require that these properties come in any sort of order, only that the properties the interface requires are present and have the required type.
Optional Properties
Not all properties of an interface may be required.Some exist under certain conditions or may not be there at all.These optional properties are popular when creating patterns like “option bags” where you pass an object to a function that only has a couple of properties filled in.
Here’s an example of this pattern:
tsTry
interfaceSquareConfig {color ?:string;width ?:number;}functioncreateSquare (config :SquareConfig ): {color :string;area :number } {letnewSquare = {color :"white",area :100 };if (config .color ) {newSquare .color =config .color ;}if (config .width ) {newSquare .area =config .width *config .width ;}returnnewSquare ;}letmySquare =createSquare ({color :"black" });
Interfaces with optional properties are written similar to other interfaces, with each optional property denoted by a?
at the end of the property name in the declaration.
The advantage of optional properties is that you can describe these possibly available properties while still also preventing use of properties that are not part of the interface.For example, had we mistyped the name of thecolor
property increateSquare
, we would get an error message letting us know:
tsTry
interfaceSquareConfig {color ?:string;width ?:number;}functioncreateSquare (config :SquareConfig ): {color :string;area :number } {letnewSquare = {color :"white",area :100 };if (Property 'clor' does not exist on type 'SquareConfig'. Did you mean 'color'?2551Property 'clor' does not exist on type 'SquareConfig'. Did you mean 'color'?config .) { clor // Error: Property 'clor' does not exist on type 'SquareConfig'Property 'clor' does not exist on type 'SquareConfig'. Did you mean 'color'?2551Property 'clor' does not exist on type 'SquareConfig'. Did you mean 'color'?newSquare .color =config .; clor }if (config .width ) {newSquare .area =config .width *config .width ;}returnnewSquare ;}letmySquare =createSquare ({color :"black" });
Readonly properties
Some properties should only be modifiable when an object is first created.You can specify this by puttingreadonly
before the name of the property:
tsTry
interfacePoint {readonlyx :number;readonlyy :number;}
You can construct aPoint
by assigning an object literal.After the assignment,x
andy
can’t be changed.
tsTry
letp1 :Point = {x :10,y :20 };Cannot assign to 'x' because it is a read-only property.2540Cannot assign to 'x' because it is a read-only property.p1 .=5;// error! x
TypeScript comes with aReadonlyArray<T>
type that is the same asArray<T>
with all mutating methods removed, so you can make sure you don’t change your arrays after creation:
tsTry
leta :number[] = [1,2,3,4];letro :ReadonlyArray <number> =a ;Index signature in type 'readonly number[]' only permits reading.2542Index signature in type 'readonly number[]' only permits reading.ro [0] =12;// error!Property 'push' does not exist on type 'readonly number[]'.2339Property 'push' does not exist on type 'readonly number[]'.ro .(5);// error! push Cannot assign to 'length' because it is a read-only property.2540Cannot assign to 'length' because it is a read-only property.ro .=100;// error! length The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.4104The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.= a ro ;// error!
On the last line of the snippet you can see that even assigning the entireReadonlyArray
back to a normal array is illegal.You can still override it with a type assertion, though:
tsTry
leta :number[] = [1,2,3,4];letro :ReadonlyArray <number> =a ;a =ro asnumber[];
readonly
vsconst
The easiest way to remember whether to usereadonly
orconst
is to ask whether you’re using it on a variable or a property.Variables useconst
whereas properties usereadonly
.
Excess Property Checks
In our first example using interfaces, TypeScript lets us pass{ size: number; label: string; }
to something that only expected a{ label: string; }
.We also just learned about optional properties, and how they’re useful when describing so-called “option bags”.
However, combining the two naively would allow an error to sneak in. For example, taking our last example usingcreateSquare
:
tsTry
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,};}letObject 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'?mySquare =createSquare ({:"red", colour width :100 });
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:
tsTry
letObject 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'?mySquare =createSquare ({:"red", colour width :100 });
Getting around these checks is actually really simple.The easiest method is to just use a type assertion:
tsTry
letmySquare =createSquare ({width :100,opacity :0.5 }asSquareConfig );
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:
tsTry
interfaceSquareConfig {color ?:string;width ?:number;[propName :string]:any;}
We’ll discuss index signatures in a bit, but here we’re saying aSquareConfig
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:SincesquareOptions
won’t undergo excess property checks, the compiler won’t give you an error.
tsTry
letsquareOptions = {colour :"red",width :100 };letmySquare =createSquare (squareOptions );
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:
tsTry
letsquareOptions = {colour :"red" };letType '{ colour: string; }' has no properties in common with type 'SquareConfig'.2559Type '{ colour: string; }' has no properties in common with type 'SquareConfig'.mySquare =createSquare (); squareOptions
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.
Function Types
Interfaces are capable of describing the wide range of shapes that JavaScript objects can take.In addition to describing an object with properties, interfaces are also capable of describing function types.
To describe a function type with an interface, we give the interface a call signature.This is like a function declaration with only the parameter list and return type given. Each parameter in the parameter list requires both name and type.
tsTry
interfaceSearchFunc {(source :string,subString :string):boolean;}
Once defined, we can use this function type interface like we would other interfaces.Here, we show how you can create a variable of a function type and assign it a function value of the same type.
tsTry
letmySearch :SearchFunc ;mySearch =function (source :string,subString :string):boolean {letresult =source .search (subString );returnresult > -1;};
For function types to correctly type check, the names of the parameters do not need to match.We could have, for example, written the above example like this:
tsTry
letmySearch :SearchFunc ;mySearch =function (src :string,sub :string):boolean {letresult =src .search (sub );returnresult > -1;};
Function parameters are checked one at a time, with the type in each corresponding parameter position checked against each other.If you do not want to specify types at all, TypeScript’s contextual typing can infer the argument types since the function value is assigned directly to a variable of typeSearchFunc
.Here, also, the return type of our function expression is implied by the values it returns (herefalse
andtrue
).
tsTry
letmySearch :SearchFunc ;mySearch =function (src ,sub ) {letresult =src .search (sub );returnresult > -1;};
Had the function expression returned numbers or strings, the type checker would have made an error that indicates return type doesn’t match the return type described in theSearchFunc
interface.
tsTry
letmySearch :SearchFunc ;Type '(src: string, sub: string) => string' is not assignable to type 'SearchFunc'. Type 'string' is not assignable to type 'boolean'.2322Type '(src: string, sub: string) => string' is not assignable to type 'SearchFunc'. Type 'string' is not assignable to type 'boolean'.=function ( mySearch src ,sub ) {letresult =src .search (sub );return"string";};
Indexable Types
Similarly to how we can use interfaces to describe function types, we can also describe types that we can “index into” likea[10]
, orageMap["daniel"]
.Indexable types have anindex signature that describes the types we can use to index into the object, along with the corresponding return types when indexing.
Let’s take an example:
tsTry
interfaceStringArray {[index :number]:string;}letmyArray :StringArray ;myArray = ["Bob","Fred"];letmyStr :string =myArray [0];
Above, we have aStringArray
interface that has an index signature.This index signature states that when aStringArray
is indexed with anumber
, it will return astring
.
There are four types of supported index signatures: string, number, symbol and template strings.It is possible to support many types of indexers, but 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.
tsTry
interfaceAnimal {name :string;}interfaceDog extendsAnimal {breed :string;}// Error: indexing with a numeric string might get you a completely separate type of Animal!interfaceNotOkay {['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 :number]:Animal ;[x :string]:Dog ;}
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:
tsTry
interfaceNumberDictionary {[index :string]:number;length :number;// ok, length is a numberProperty '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'.:string;// error, the type of 'name' is not a subtype of the indexer name }
However, properties of different types are acceptable if the index signature is a union of the property types:
tsTry
interfaceNumberOrStringDictionary {[index :string]:number |string;length :number;// ok, length is a numbername :string;// ok, name is a string}
Finally, you can make index signaturesreadonly
in order to prevent assignment to their indices:
tsTry
interfaceReadonlyStringArray {readonly [index :number]:string;}letmyArray :ReadonlyStringArray = ["Alice","Bob"];Index signature in type 'ReadonlyStringArray' only permits reading.2542Index signature in type 'ReadonlyStringArray' only permits reading.myArray [2] ="Mallory";// error!
You can’t setmyArray[2]
because the index signature isreadonly
.
Indexable Types with Template Strings
A template string can be used to indicate that a particular pattern is allowed, but not all. For example, a HTTP headers object may have a set list of known headers and support anycustom defined properties which are prefixed withx-
.
tsTry
interfaceHeadersResponse {"content-type":string,date :string,"content-length":string// Permit any property starting with 'x-'.[headerName :`x-${string}`]:string;}functionhandleResponse (r :HeadersResponse ) {// Handle known, and x- prefixedconsttype =r ["content-type"]constpoweredBy =r ["x-powered-by"]// Unknown keys without the prefix raise errorsconstProperty 'origin' does not exist on type 'HeadersResponse'.2339Property 'origin' does not exist on type 'HeadersResponse'.origin =r .origin }
Class Types
Implementing an interface
One of the most common uses of interfaces in languages like C# and Java, that of explicitly enforcing that a class meets a particular contract, is also possible in TypeScript.
tsTry
interfaceClockInterface {currentTime :Date ;}classClock implementsClockInterface {currentTime :Date =newDate ();constructor(h :number,m :number) {}}
You can also describe methods in an interface that are implemented in the class, as we do withsetTime
in the below example:
tsTry
interfaceClockInterface {currentTime :Date ;setTime (d :Date ):void;}classClock implementsClockInterface {currentTime :Date =newDate ();setTime (d :Date ) {this.currentTime =d ;}constructor(h :number,m :number) {}}
Interfaces describe the public side of the class, rather than both the public and private side.This prohibits you from using them to check that a class also has particular types for the private side of the class instance.
Difference between the static and instance sides of classes
When working with classes and interfaces, it helps to keep in mind that a class hastwo types: the type of the static side and the type of the instance side.You may notice that if you create an interface with a construct signature and try to create a class that implements this interface you get an error:
tsTry
interfaceClockConstructor {new (hour :number,minute :number);}classClass 'Clock' incorrectly implements interface 'ClockConstructor'. Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): any'.2420Class 'Clock' incorrectly implements interface 'ClockConstructor'. Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): any'.implements Clock ClockConstructor {currentTime :Date ;constructor(h :number,m :number) {}}
This is because when a class implements an interface, only the instance side of the class is checked.Since the constructor sits in the static side, it is not included in this check.
Instead, you would need to work with the static side of the class directly.In this example, we define two interfaces,ClockConstructor
for the constructor andClockInterface
for the instance methods.Then, for convenience, we define a constructor functioncreateClock
that creates instances of the type that is passed to it:
tsTry
interfaceClockConstructor {new (hour :number,minute :number):ClockInterface ;}interfaceClockInterface {tick ():void;}functioncreateClock (ctor :ClockConstructor ,hour :number,minute :number):ClockInterface {returnnewctor (hour ,minute );}classDigitalClock implementsClockInterface {constructor(h :number,m :number) {}tick () {console .log ("beep beep");}}classAnalogClock implementsClockInterface {constructor(h :number,m :number) {}tick () {console .log ("tick tock");}}letdigital =createClock (DigitalClock ,12,17);letanalog =createClock (AnalogClock ,7,32);
BecausecreateClock
’s first parameter is of typeClockConstructor
, increateClock(AnalogClock, 7, 32)
, it checks thatAnalogClock
has the correct constructor signature.
Another simple way is to use class expressions:
tsTry
interfaceClockConstructor {new (hour :number,minute :number):ClockInterface ;}interfaceClockInterface {tick ():void;}constClock :ClockConstructor =classClock implementsClockInterface {constructor(h :number,m :number) {}tick () {console .log ("beep beep");}};letclock =newClock (12,17);clock .tick ();
Extending Interfaces
Like classes, interfaces can extend each other.This allows you to copy the members of one interface into another, which gives you more flexibility in how you separate your interfaces into reusable components.
tsTry
interfaceShape {color :string;}interfaceSquare extendsShape {sideLength :number;}letsquare = {}asSquare ;square .color ="blue";square .sideLength =10;
An interface can extend multiple interfaces, creating a combination of all of the interfaces.
tsTry
interfaceShape {color :string;}interfacePenStroke {penWidth :number;}interfaceSquare extendsShape ,PenStroke {sideLength :number;}letsquare = {}asSquare ;square .color ="blue";square .sideLength =10;square .penWidth =5.0;
Hybrid Types
As we mentioned earlier, interfaces can describe the rich types present in real world JavaScript.Because of JavaScript’s dynamic and flexible nature, you may occasionally encounter an object that works as a combination of some of the types described above.
One such example is an object that acts as both a function and an object, with additional properties:
tsTry
interfaceCounter {(start :number):string;interval :number;reset ():void;}functiongetCounter ():Counter {letcounter =function (start :number) {}asCounter ;counter .interval =123;counter .reset =function () {};returncounter ;}letc =getCounter ();c (10);c .reset ();c .interval =5.0;
When interacting with 3rd-party JavaScript, you may need to use patterns like the above to fully describe the shape of the type.
Interfaces Extending Classes
When an interface type extends a class type it inherits the members of the class but not their implementations.It is as if the interface had declared all of the members of the class without providing an implementation.Interfaces inherit even the private and protected members of a base class.This means that when you create an interface that extends a class with private or protected members, that interface type can only be implemented by that class or a subclass of it.
This is useful when you have a large inheritance hierarchy, but want to specify that your code works with only subclasses that have certain properties.The subclasses don’t have to be related besides inheriting from the base class.For example:
tsTry
classControl {privatestate :any;}interfaceSelectableControl extendsControl {select ():void;}classButton extendsControl implementsSelectableControl {select () {}}classTextBox extendsControl {select () {}}classClass 'ImageControl' incorrectly implements interface 'SelectableControl'. Types have separate declarations of a private property 'state'.2420Class 'ImageControl' incorrectly implements interface 'SelectableControl'. Types have separate declarations of a private property 'state'.implements ImageControl SelectableControl {privatestate :any;select () {}}
In the above example,SelectableControl
contains all of the members ofControl
, including the privatestate
property.Sincestate
is a private member it is only possible for descendants ofControl
to implementSelectableControl
.This is because only descendants ofControl
will have astate
private member that originates in the same declaration, which is a requirement for private members to be compatible.
Within theControl
class it is possible to access thestate
private member through an instance ofSelectableControl
.Effectively, aSelectableControl
acts like aControl
that is known to have aselect
method.TheButton
andTextBox
classes are subtypes ofSelectableControl
(because they both inherit fromControl
and have aselect
method). TheImageControl
class has its ownstate
private member rather than extendingControl
, so it cannot implementSelectableControl
.
The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request ❤
Last updated: Jul 14, 2025