TypeScript interface vs. type
I’m not an expert in the field of TypeScript by any means but I have worked with it every single day for the last few months and I am really enjoying the ride. Beneath its straight-forward set of features there are some confusing concepts as well. Should it be an interface
or a type
alias? This is a question asked a lot by newcomers.
interface DudeInterface {
name: string;
age: number;
}
const pawel: DudeInterface = {
name: "Pawel",
age: 31
};
type DudeType = {
name: string,
age: number
};
const pawel: DudeType = {
name: "Pawel",
age: 31
};
Both methods are correct to describe a structure of an object but which one should we use? As always — it depends. Let me compare and contrast them.
Misleading section of the official TypeScript Handbook
The “Interfaces vs. Type Aliases” section of the official TypeScript Handbook explains the characteristics and differences between both of them.
- Interfaces create a new name, type aliases don’t
- Type aliases cannot be extended or implemented from
Since June 2016 when this part of the documentation was last updated, TypeScript has had a major version bump and lots of functionality has changed. Unfortunately none of these points are true anymore. It is a great time to update this obsolete part of the documentation. I will try to do a better job at explaining the difference. Hopefully the TypeScript Handbook will be updated eventually, then I will get rid of this section of the article.
Microsoft actively works on a brand new TypeScript Handbook that does a much better job at explaining the subject. It is a work in progress and we don’t know the date when it is going to replace the current Handbook.
Interfaces are restricted to an object type
Interface declarations can exclusively represent the shape of an object-like data structures. Type alias declarations can create a name for all kind of types including primitives (undefined
, null
, boolean
, string
and number
), union, and intersection types. In a way, this difference makes the type
more flexible. In theory every type declaration that you can express with an interface
, you can recreate using a type
alias. Lets have a look at an example that can be represented using a type
alias but is beyond the power of an interface
.
type info = string | { name: string };
You can merge interfaces but not types
Multiple declarations with the same name are valid only when used with interface
. Doing so doesn’t override previous one but produces a merged result containing members from all declarations.
interface DudeInterface {
name: string;
}
interface DudeInterface {
age: number;
}
const pawel: DudeInterface = {
name: "Pawel Grzybek",
age: 31
};
Attempting to merge type
s results in a Duplicate identifier
compiler error.

Type aliases can use computed properties
The in
keyword can be used to iterate over all of the items in an union of keys. We can use this feature to programmatically generate mapped types. Have a look at this example using type
aliases.
type Keys = "firstname" | "surname"
type DudeType = {
[key in Keys]: string
}
const test: DudeType = {
firstname: "Pawel",
surname: "Grzybek"
}
Unfortunately we cannot take advantage of computed properties in an interface
declaration.

Deferred type resolution of interfaces vs. eager type aliases
This is no longer truth. Since I wrote this article, TypeScript behavior changed slightly and now the resolution of both (types and interfaces) happens in the same phase. Looks like both of them are deferred so the example from the image below is now perfectly valid TypeScript code.
Another difference is when a type is resolved by the compiler. Resolution of an interface
is deferred, means that you can use them to recursively chain types. Resolution of type
aliases is eager and compiler goes crazy when you try to resolve recursively nested types. Look!
type Dude = string | Pals;
interface Pals extends Array<Dude> {}
We are allowed to do it, because type of interface
s is deferred. Equivalent with type
alias results with Type alias circularly references itself
compiler error.

Be consistent
Just because in many situations you can use either of them, it doesn’t mean you should use them interchangeably. As an active open source contributor I see some advantages of using interface
for authoring a public API and I tend to use it more often. The type
alias is irreplaceable in some circumstances mentioned in this article. Most importantly — keep it consistent. Hopefully this article helped you out.
Insightful article Mr Pawel. I know when to use types now. 😉
Thank you for kind words Mr Gregory :)
Great read.
Thank you very much :)
Why are you waiting for the handbook to be updated. Just do it yourself: https://github.com/microsof...
Also, great read. I think consensus amongst devs is to use `interface` whenever you can. It's in the default tslint ruleset.
You are not the first one who suggested me to contribute to a handbook. You know what — I may give it a go :-)
Thanks for your kind words and have a great day 🥑
I love avocado! Thanks! 🧡
Good article. Thanks
Good stuff Pawel!
An addition to this article that I think would make it a little better is to explain what an interface is and what a type is before comparing and contrasting them.
Explain that an interface is a promise or contract to implement a data shape, you kinda touch on this but it's definitely worth further explanation. You should do the same for types as well, as a type is a definition of a type of data that can be declared and assigned.
Many people use interfaces as types, which isn't strictly the correct way to use them, certainly not in other languages anyways.
Using a type the correct way
type Communication = string | { message: string };
const comm1: Communication = 'lol';
const comm2: Communication = { message: 'lol' };
Using a type the wrong way
type Contract = {
message: string;
print: Function;
}
// This is syntactically correct, but since when do people implement a type?
// you declare and instantiate types, we implement interfaces as that's the purpose of polymorphism!
class MessageContract implements Contract {
constructor(public message: string) { }
print() {
console.log(this.message);
}
}
Using an interface the correct way by defining an interface and implementing it in a class
interface Contract {
message: string;
print: Function;
}
class Messager implements Contract {
constructor(public message: string) { }
print() {
console.log(this.message);
}
}
const messager = new Messager('Hello World!);
messager.print();
Using an interface the incorrect way by using it as a type
interface Messager {
print: Function;
}
const messager: Messager = {
print: () => {
console.log('Hello!')
}
}
messager.print();
I personally think the only other appropriate usage of an interface is to define the shape of arguments to a function as this is still technically a contract or promise of a data shape that the function expects.
interface PropTypes {
name: string;
age: number;
}
const funkyFunc = (props: PropTypes) => {
const { name, age } = props;
console.log(`Hello ${name}, you're ${age} old!`);
}
Another key difference not expanded on enough imho is the deferred type resolution of interfaces. Interfaces can extend classes as well as types. The interface will inherit the properties and their types of a class when this is done which is super neat, I guess it's the point of what you're saying?
Types can't do this at all.
class Messager {
sayHello() {
console.log('Hello!);
}
}
interface AwesomeMessager extends Messager {
name: string;
}
class MessageLogger implements AwesomeMessager {
constructor(public name: string) {}
sayHello() {
console.log(`Hello ${this.name}!`);
}
}
Anyway, great article and keep 'em coming 💪
Wow dude 😲
Copy / paste = new article :-)
Thanks for clarification.
I'm not sure I agree with the "Using an interface the incorrect way by using it as a type" part. This pattern:
interface Foo { foo: string, bar: string }
var o: Foo = { foo: "foo", bar: "bar" };
is pretty common and used in a lot of places. If we should consider this as an anti-pattern, a lot of code would become more convoluted. So I would like a more detailed rationale.
Maybe an alternative could be:
var o: Foo = { foo: "foo", bar: "bar" } as Foo;
Does the official TypeScript documentation say anything about this?
Good article, thanks!
Do you have any thoughts on the 'no-type-alias' rule that is on by default in typescript-eslint:
https://github.com/typescri...
Generally I avoid overriding default rules but after reading your article...
Also, just a syntax issue, but shouldn't your type elements be deliniated with a semicolon instead of a comma (eg. for DudeType)?
Hi.
I am not sure why this is a recommended rule. It would be worth to ask why author of this rule consider it as a bad practice.
Re syntax, same like you I like defaults and this is a Prettier default setting and I keep using it. I have no other reason :)
Thanks for reading !
The comma vs. semicolon thing is an Typescript linting rule (member-delimiter-style). In a sense the 'author' of these is Microsoft because the linter I'm using (typescript-eslint) is now the official TS linter.
Any of these rules can be overridden, of course, but it is worth being aware of them. And, for the record, another rule that is relevant to all this is 'prefer-interface'.
The `member-delimiter-style` rule is one of those that came from TS Lint community. Microsoft has nothing to do with ESLint now, just officially announced that ESLint is the way to go.
https://pawelgrzybek.com/li...
Thanks for the article! FYI, as of TypeScript v3.7, type allows circular references:
type Dude = string | Pals;
type Pals = Dude[];
type Result = "valid" | "invalid" | Promise<Result>;
Try it on the TS playground.
Issue: https://github.com/microsof...
PR: https://github.com/microsof...
I skipped this news from TS 3.7 announcement article. Thanks a lot!
Note with 3.8, this seems to work
type Dude = string | Pals;
type Pals = Dude[];
Could you explain a bit what it means when you say that the "resolution of an `interface` is deferred" and the "resolution of `type` aliases is eager? I coudn't find any article about how typescript resolves its `interface`s and `type` aliases.
Sorry for a late reply.
This behavior of type resolution is no longer truth. It has been changed in one of the recent versions of TypeScript and now types and interfaces are resolved in the same compilation phase. Both of them are using deferred type resolutions.
I want to build a blog of your type. Is there any tutorial? Thank you very much.
Thanks very much, I. think I'd learn a lot.