Interfaces and type aliases
Two ways to name a shape, and the one difference that decides which to use.
After this lesson you can
- Name an object shape with either form
- Say what an interface can do that a type alias cannot
- Mark a property optional or readonly and know what that guarantees
Both of these describe the same object:
interface User {
id: string;
email: string;
name?: string; // may be absent
readonly createdAt: Date;
}
type UserAlias = {
id: string;
email: string;
name?: string;
readonly createdAt: Date;
};
name? means the property may be missing, so its type is
string | undefined and you have to handle that. readonly stops
assignment after construction — at compile time only; nothing prevents it at
runtime.
The difference that matters
An interface is open: declaring it twice merges the declarations.
interface Window { myThing: string } // adds to the existing Window
A type alias is closed: declaring it twice is an error. It can, however, name things an interface cannot — a union, a tuple, a mapped or conditional type:
type Status = "draft" | "live"; // interface cannot
type Pair = [string, number]; // interface cannot
So: interface for an object shape others may need to extend, especially a
public API or a global. type for everything else. Beyond that the two are
interchangeable, and a codebase that picks one and sticks to it is easier to
read than one that agonises.
Try it
interface User { id: string; email: string; name?: string; readonly createdAt: string;} function display(u: User): string { // name may be undefined, so the compiler makes you deal with it. return `${u.name ?? u.email} (since ${u.createdAt})`;} export function run(): string[] { return [ display({ id: "1", email: "nino@example.com", createdAt: "2024-01-01" }), display({ id: "2", email: "ana@example.com", name: "Ana", createdAt: "2024-06-01" }), ];}Structural typing
TypeScript does not care what you called a type. If a value has the right shape, it fits:
function greet(u: { name: string }) { return `Hi ${u.name}`; }
greet({ name: "Nino", extra: 1 }); // a variable of this shape is fine
Passing an object literal directly is the exception: excess property
checking rejects extra there, on the grounds that you probably made a typo.
Try it yourself
2 visible tests · 2 hidden testsImplement initials(user: { name?: string; email: string }): string.
When name is present, return one uppercase letter per word ("Nino Beridze" → "NB"). When it is absent, fall back to the first two
letters of the email's local part, uppercased ("ana@example.com" →
"AN").
initials({"name":"Nino Beridze","email":"nino@example.com"})initials({"email":"ana@example.com"})
Sign up to check the hidden tests and save your progress. Sign up