TypeScript

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

Optional and readonly, in practicetypescript-5
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" }),  ];}
What to look for

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 tests

Implement 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"})
Loading editor…

Sign up to check the hidden tests and save your progress. Sign up