Interfaces and Type Aliases

Reviewed & published by Brayan K

By the end of this lesson you'll be able to describe the exact shape of any object with an interface — required, optional, and read-only properties, methods, and inheritance — and know exactly when to reach for a type alias instead.

Part of the free TypeScript course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

An interface is a contract — or a blueprint. A house blueprint says "there must be a kitchen, three bedrooms, and a 2 metre door" without laying a single brick. Any builder who follows it produces a house that fits. In the same way, an interface says "any object claiming to be a User must have an id (number), a name (string), and so on." TypeScript then checks every object against that contract before your program runs — so a typo or a missing field is caught at your desk, not by a user.

1. Describing an Object's Shape

An interface is a named description of what an object must contain. You list each property and its type. Once an object is annotated with that interface, TypeScript guarantees every listed property exists and has the right type. Here's the real TypeScript syntax — read every comment:

// An interface describes the SHAPE an object must have.
interface User {
  id: number;        // required: every User must have a number id
  name: string;      // required: must be text
  email: string;     // required: must be text
  isActive: boolean; // required: true or false
}

// This object matches the shape, so TypeScript is happy.
const alice: User = {
  id: 1,
  name: "Alice",
  email: "[email protected]",
  isActive: true,
};

// Missing "email" -> error TS2741 (see Common Errors below):
// const bob: User = { id: 2, name: "Bob", isActive: true };

At runtime the interface vanishes and you're left with a plain JavaScript object. Run this worked example to see the shape come to life — the comments state exactly what each line prints:

// The interface lives only in TypeScript. At runtime it disappears,
// leaving a plain JS object that MATCHES the shape we promised.
//
// interface User {
//   id: number;
//   name: string;
//   email: string;
//   isActive: boolean;
//   bio?: string;        // optional
//   readonly joinedAt: string;
// }

const user = {
  id: 1,
  name: "Alice",
  email: "[email protected]",
  isActive: true,
  joinedAt: "2024-03-01",   // readonly: we set it once, here
  // bio is omitted — that's allowed because it is optional (?)
};

console.log("id:        " + user.id);          // id:        1
console.log("name:      " + user.name);        // name:      Alice
console.log("isActive:  " + user.isActive);    // isActive:  true
console.log("bio set?   " + (user.bio !== undefined)); // bio set?   false

// "readonly" is a COMPILE-TIME rule. TypeScript would reject the next line:
//   user.joinedAt = "2025-01-01";  // ❌ error TS2540
// At runtime JS doesn't enforce it, but your editor caught it before you ran.
console.log("joinedAt:  " + user.joinedAt);    // joinedAt:  2024-03-01

2. Optional ? and readonly Properties

Not every property is required. Add a ? after the name to make a property optional — objects may include it or leave it out. Add readonly before a property to lock it after creation: you can set it once, but any later assignment is a compile error. This is how you model "an id is fixed forever, but a bio can be added later."

interface UserProfile {
  name: string;        // required
  email: string;       // required
  readonly id: number; // set once, then locked
  bio?: string;        // optional — the ? means "may be missing"
  avatar?: string;     // optional
}

const p: UserProfile = { id: 7, name: "Alice", email: "[email protected]" };
// bio and avatar were omitted — that's fine, they're optional.

p.bio = "Developer"; // ✅ ok to add later
p.id = 99;           // ❌ error TS2540: 'id' is read-only and cannot change

Your turn. The program below builds an object that must match a Book shape (with one readonly and one optional field). Fill in the two blanks marked ___, then run it.

// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
//
// You're modelling a Book for a library app. In TypeScript the shape is:
//
//   interface Book {
//     readonly isbn: string;  // set once, never changes
//     title: string;          // required
//     pages: number;          // required
//     subtitle?: string;      // optional
//   }
//
// Build a matching object below.

const book = {
  isbn: "978-0131103627",
  title: ___,        // 👉 a book title in "double quotes"
  pages: ___,        // 👉 a whole number of pages, e.g. 272
  // subtitle is optional — leave it out, that's allowed
};

console.log("Title: " + book.title + " (" + book.pages + " pages)");
console.log("Has subtitle? " + (book.subtitle !== undefined));

// ✅ Expected output (example):
//    Title: The C Programming Language (272 pages)
//    Has subtitle? false

3. Method Signatures & Extending Interfaces

Interfaces aren't just for data — they can require methods too. A method signature like speak(): string says "this object must have a function called speak that returns a string." And extends lets you build a bigger interface on top of a smaller one: a Dog gets everything an Animal has, plus its own extras. You can even extend several interfaces at once to compose shapes together.

// Method signatures: describe a function a property must provide.
interface Animal {
  name: string;
  speak(): string;            // a method that takes nothing, returns a string
  feed(amount: number): void; // takes a number, returns nothing
}

// "extends" builds a bigger interface from a smaller one (inheritance).
interface Dog extends Animal {
  breed: string;              // Dog has name + speak + feed AND breed
}

// You can extend MORE than one at a time:
interface Timestamped { createdAt: Date; }
interface Post extends User, Timestamped {
  title: string;             // Post has every User + Timestamped field, plus title
}

Run this to see an object that satisfies an extended interface (with a method) behaving at runtime:

// interface Animal { name: string; speak(): string; }
// interface Dog extends Animal { breed: string; }
//
// A Dog object must supply EVERY Animal member plus its own.

const rex = {
  name: "Rex",
  breed: "Beagle",
  speak() { return this.name + " says Woof!"; }, // satisfies speak(): string
};

console.log(rex.name);      // Rex
console.log(rex.breed);     // Beagle
console.log(rex.speak());   // Rex says Woof!

// Index signature in action: a dictionary of string -> string.
// interface StringMap { [key: string]: string; }
const env = { NODE_ENV: "production", PORT: "3000" };
for (const key in env) {
  console.log(key + " = " + env[key]);
}
// NODE_ENV = production
// PORT = 3000

Now you add a method. A Square extends Shape, so it must provide an area() method. Fill in the blank so it returns the correct area:

// 🎯 YOUR TURN — fill in the blanks.
//
// In TypeScript:
//   interface Shape { name: string; area(): number; }
//   interface Square extends Shape { side: number; }
//
// A Square must have name, side, AND an area() method.

const square = {
  name: "tile",
  side: 4,
  // 👉 add a method "area" that returns side * side
  area() {
    return ___;     // 👉 multiply this.side by this.side
  },
};

console.log(square.name + " area = " + square.area());

// ✅ Expected output:
//    tile area = 16

4. Index Signatures

Sometimes you don't know the property names ahead of time — you just know they'll all be strings mapping to, say, strings. An index signature [key: string]: string describes exactly that: "any string key is allowed, and its value must be a string." It's perfect for dictionaries, config maps, and translation tables.

// An index signature says "any key of this type maps to that value type".
interface StringMap {
  [key: string]: string;   // any string key -> a string value
}

const env: StringMap = {
  NODE_ENV: "production",
  API_KEY: "abc123",
  PORT: "3000",
};
env.REGION = "eu-west-1"; // ✅ any new string key is allowed
// env.count = 5;         // ❌ value must be a string, not a number

5. interface vs type — Which to Use?

A type alias (type) gives a name to any type — not just object shapes. That includes unions (string | number), intersections (A & B), literal types, tuples, and function types — things an interface simply can't express. For plain object shapes, both work and read almost identically.

// type alias: a name for ANY type, not just object shapes.
type Point = { x: number; y: number };       // object shape
type ID = string | number;                    // union — interface CANNOT do this
type Direction = "up" | "down" | "left" | "right"; // literal union
type Callback = (data: string) => void;       // function type

// Both can describe an object shape:
interface PointI { x: number; y: number }     // same idea, interface style

// Rule of thumb:
//  • interface  -> object shapes & things classes implement (it can be extended/merged)
//  • type       -> unions, intersections, primitives, tuples, function types

🔎 Deep Dive: the decision in one table

Can it…interfacetype
Describe an object shape✅✅
Be extended / inherited✅ extends✅ &
Express a union (A | B)❌✅
Name a primitive / tuple / function❌✅
Merge two declarations of the same name✅❌

Rule of thumb: reach for interface for object shapes and public APIs (it can be extended and merged later). Reach for type when you need a union, an intersection, a primitive alias, a tuple, or a function type. When in doubt for an object shape, either is fine — pick one and stay consistent.

Common Errors (and the fix)

Pro Tips

📋 Quick Reference

PatternSyntax
Interfaceinterface User { name: string }
Optional propertybio?: string
Readonly propertyreadonly id: number
Method signaturespeak(): string
Extendinterface Dog extends Animal {}
Index signature[key: string]: string
Type alias / uniontype ID = string | number
Intersectiontype C = A & B

Frequently Asked Questions

Q: Does an interface exist when my code runs?

No. Interfaces (and type aliases) are erased during compilation — they're purely a compile-time contract. That's why the runnable demos here are plain JavaScript: the type checking happens in your editor and build step, not at runtime.

Q: When should I use interface vs type?

Use interface for object shapes and anything a class will implement — it can be extended and merged. Use type when you need a union, intersection, primitive alias, tuple, or function type. For a plain object shape either is fine; just be consistent.

Q: What's the difference between ? and readonly?

? controls whether a property must exist (optional vs required). readonly controls whether it can change after it's set. They're independent — a property can be both, neither, or either.

Q: Why did TypeScript reject an extra property it could have ignored?

That's the excess property check (TS2353). When you assign an object literal directly to a typed target, TypeScript flags unknown properties to catch typos. Assign through an intermediate variable, or add the property to the interface, if the extra field is intentional.

Mini-Challenge: a typed Product

No blanks this time — just a brief and an outline. Build an object that satisfies the Product interface (a readonly field, an optional field, and a method), then print its description. Check your output against the example in the comments.

// 🎯 MINI-CHALLENGE: a typed Product
//
// In TypeScript you'd write this interface (you don't have to retype it):
//   interface Product {
//     readonly sku: string;   // unique code, never changes
//     name: string;
//     price: number;
//     onSale?: boolean;       // optional
//     describe(): string;     // method returning a string
//   }
//
// 1. Create an object "product" with: sku, name, price (a number),
//    and a describe() method.
// 2. describe() should return:  name + " — $" + price
// 3. Print product.describe(), then print whether onSale is set.
//
// ✅ Example output:
//    Keyboard — $49.99
//    On sale? false

// your code here

🎉 Lesson Complete

Practice quiz

What does an interface describe?

  • A runtime class instance
  • A function's source code
  • The shape an object must have
  • A CSS layout

Answer: The shape an object must have. An interface is a named description of the properties and types an object must contain.

How do you make a property optional in an interface?

  • Add a ? after the property name
  • Prefix it with optional
  • Wrap it in brackets
  • Set it to null

Answer: Add a ? after the property name. A ? after the name (bio?: string) makes the property optional; objects may omit it.

What does the readonly modifier do?

  • Hides the property
  • Makes the property optional
  • Converts it to a string
  • Locks the property so it cannot be reassigned after creation

Answer: Locks the property so it cannot be reassigned after creation. readonly lets you set a property once; any later assignment is a compile error (TS2540).

Which keyword builds a bigger interface from a smaller one?

  • implements
  • extends
  • inherits
  • merge

Answer: extends. interface Dog extends Animal gives Dog every Animal member plus its own.

Which can an interface NOT express that a type alias can?

  • A union like string | number
  • An object shape
  • A method signature
  • A readonly property

Answer: A union like string | number. Only a type alias can name a union; interfaces describe object shapes, not unions.

What does the index signature [key: string]: string mean?

  • Only the key named 'key' is allowed
  • Keys must be numbers
  • Any string key maps to a string value
  • The object is frozen

Answer: Any string key maps to a string value. An index signature says any string key is allowed and each value must be a string.

Do interfaces exist at runtime?

  • Yes, they are real objects
  • No, they are erased during compilation
  • Only in strict mode
  • Only when extended

Answer: No, they are erased during compilation. Interfaces (and type aliases) are compile-time only; they are erased before the code runs.

What is the excess property check (TS2353)?

  • A warning about unused variables
  • A check that arrays are not too long
  • A rule about function arity
  • Flagging unknown properties on a directly assigned object literal

Answer: Flagging unknown properties on a directly assigned object literal. Assigning an object literal directly flags any extra field not in the type, catching typos.

Which is a valid method signature inside an interface?

  • speak: string()
  • speak(): string
  • string speak()
  • method speak string

Answer: speak(): string. speak(): string means the object must have a function speak that returns a string.

Which declaration capability is unique to interface (not type)?

  • Describing an object shape
  • Having optional properties
  • Declaration merging of two same-named declarations
  • Using readonly

Answer: Declaration merging of two same-named declarations. Two interfaces with the same name merge; type aliases cannot be redeclared this way.

Continue this course