Many developers are confused when choosing between a TypeScript interface or a type. This is probably because they’re very similar with minor differences.
Let’s explore some sensible practices and defaults, whilst uncovering some hidden gems in the TypeScript language and answer the question “Should I use an interface or a type?”
Once you’re finished, check out my other article on TypeScript Interfaces vs Classes!
Here’s my rule: For use cases such as creating new types through things like primitives, union types, and tuple types, I prefer to use the type
keyword. For anything else (objects/arrays), it’s an interface
.
An interface
is extremely helpful when dealing with data structures as they’re a very visual representation (albeit so is type
, this is typically my preference). It’s completely okay to choose a type
for your objects and arrays too.
Nevertheless, let’s uncover some more about Types vs Interfaces in TypeScript so you can make a more informed decision.
Objects: Type vs Interface
Typically we would define a data structure to model a type against our intended variables and function arguments. Most of the time a data structure is an array or an object that might contain a few methods.
Lets create an object as an interface (recommended approach):
interface Milkshake {
name: string;
price: number;
getIngredients(): string[];
}
Here, I prefer using an interface because it’s clear it’s an interface and not a variable with data assigned - a nice readability win. We’re using name
and price
to allow setting records and getIngredients
as our method call.
Free eBook
Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- Setters and Getters for Styles and Class Bindings
When we compare that to a type - it could easily be confused as an actual object due to the assignment =
:
type Milkshake = {
name: string;
price: number;
getIngredients(): string[];
};
This is just my small preference for choosing an interface
over using a type
here - but you’re free to use the type
keyword if you wish.
Intersecting: Type vs Interface
Intersecting simply means to combine one or more types! This pattern can be accomplished using both an interface
or type
.
Let’s assume the following interfaces, I’ll show you how we can intersect an interface
and type
:
interface MilkshakeProps {
name: string;
price: number;
}
interface MilkshakeMethods {
getIngredients(): string[];
}
With an interface, we would intersect by using the extends
keyword. I’m using multiple interfaces to show you how to extend (inherit) into a final Milkshake
interface containing all properties and methods defined elsewhere:
// { name: string, price: number, getIngredients(): string[] }
interface Milkshake extends MilkshakeProps, MilkshakeMethods {}
We can now use Milkshake
anywhere we like and can benefit from having name
, price
and getIngredients
in just one interface reference.
Here’s how we’d do the same with a type
and intersecting the types via &
:
// { name: string, price: number, getIngredients(): string[] }
type Milkshake = MilkshakeProps & MilkshakeMethods;
I’d recommend using a type
instead of an interface
when you want to intersect types. Using extends
feels a bit more verbose and not as clear to read and I feel this is what the type
keyword was made for.
It’s also super easy to just combine more types with type
. I find the code is clearer than using extends
with intefaces.
Interface are also limited - the type
alias can be used for more complex types such as tuples, primitives, unions and other more:
// { name: string, price: number, getIngredients(): string[] }
type Milkshake = MilkshakeProps & { getIngredients(): string[] };
Primitives: Type vs Interface
Let’s talk primitive types. Yes, strings, numbers etc. You can only assign a primitive type to a type
alias:
type MilkshakeName = string;
interface Milkshake {
name: MilkshakeName;
price: number;
}
If you need to use a primitive, use a type
. Interfaces are a no-go here for just a single value as the syntax doesn’t support it.
Free eBook
Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- Setters and Getters for Styles and Class Bindings
For most cases though, it would easier to just use the primitive value directly:
interface Milkshake {
name: string;
price: number;
}
Don’t overcomplicate your code!
Classes: Type vs Interface
Whether you’ve chosen a type
or interface
the way we use it with a class is the same:
type Size = {
size: string
};
interface Milkshake {
name: string;
price: number;
getIngredients(): string[];
}
class Order implements Size, Milkshake {
// Size
size = 'large';
// Milkshake
name = 'Vanilla';
price = 399;
getIngredients() {
return ['vanilla', 'ice'];
}
}
Classes do not support implementing/extending union types, because they are considered to be static blueprints. This means you need to be super explicit about each type you implement, as it cannot be dynamic or change right now due to TypeScript limitations.
Learn more about TypeScript Interfaces vs Classes!
Functions: Type vs Interface
Typically I would create a function using the type
alias as most of the time we would want to type an anonymous function:
type IngredientsFn = () => string[];
const getIngredients: IngredientsFn = () => ['vanilla', 'ice'];
However, if using an interface
for this would be more your style, here’s how to do that:
interface IngredientsFn {
() => string[];
}
It just feels a bit wonky compared to using type
, but again both are completely valid TypeScript and there’s no difference when the code is compiled.
When not to use a Type
Now we’ve explored the various comparisons and recommended approaches, it’s time to talk about Declaration Merging, a feature in TypeScript that applies to just interface
and would be a reason to choose an interface
over a type
.
To merge types using type
, we would have to do something like this and create a new final type
:
type MilkshakeProps = {
name: string;
price: number;
};
type MilkshakeMethods = {
getIngredients(): string[];
};
type Milkshake = MilkshakeProps & MilkshakeMethods;
Our type Milkshake
is now a type alias of MilkshakeProps
and MilkshakeMethods
. Note, that to achieve this behaviour we have had to create Milkshake
, giving us 3 individual type aliases.
With interfaces, there’s in fact a smarter approach (some might say a bit too clever) called declaration merging.
We can achieve a declaration merge by simply declaring the same interface
twice in the same scope (this could be either via importing the interface from another module, or declaring it locally next to another interface):
// declared once...
interface Milkshake {
name: string;
price: number;
}
// declared again the same, it works!
interface Milkshake {
getIngredients(): string[];
}
// { name: string, price: number, getIngredients(): string[] }
const milkshake: Milkshake = {
name: 'Banana',
price: 399,
getIngredients() {...}
};
Milkshake now contains name
, price
and getIngredients
! However, is declaration merging a good thing? I’m honestly not a fan and I feel it could lead to more harm than good. I’d compose your types through type
rather than use declaration merging - but at least you know the main difference(s) now.
Conclusion
So there you have it! The main differences between Types and Interfaces in TypeScript.
To recap, with some personal preferences too, I’d stick with an interface
for objects and use the type
alias keyword to compose new types on the fly. These new types could even be from interfaces or other types such as tuples, unions and intersection types. There’s a lot of possibilities, but by understanding approaches we can begin to pick the right tool.
For the most part, interfaces and types are pretty much the same besides a few differences covered here. I hope you enjoyed reading!
If you are serious about your TypeScript skills, your next step is to take a look at my TypeScript courses, they will teach you the full language basics in detail as well as many advanced use cases you’ll need in daily TypeScript development!
Happy coding!