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
- Describe an object's shape with an interface
- Mark properties optional with ? and lock them with readonly
- Add method signatures so objects must provide functions
- Build bigger interfaces by extending smaller ones
- Use index signatures for flexible string-keyed objects
- Choose interface vs type alias for the job
💡 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-012. 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 changeYour 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? false3. 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 = 3000Now 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 = 164. 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 number5. 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… | interface | type |
|---|---|---|
| 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)
- TS2741: "Property 'email' is missing in type … but required in type 'User'." — You left out a required property. Either add the missing field, or mark it optional with ? in the interface if it really can be absent.
- TS2353: "Object literal may only specify known properties, and 'age' does not exist in type 'User'." — This is the excess property check: when you assign an object literal directly, TypeScript flags any extra field. Fix the typo ({ naem: ... } → name), add the property to the interface, or assign through a variable first if the extra field is intentional.
- TS2540: "Cannot assign to 'id' because it is a read-only property." — You tried to change a readonly field after creation. Set it once when the object is built, or drop readonly if it really needs to change.
- TS2551 / TS2339: "Property 'bio' does not exist…" — You read a property the interface never declared. Add it to the interface (optional with ? if appropriate), or check your spelling.
Pro Tips
- 💡 Name interfaces like nouns: User, OrderLine, Config — they describe a thing.
- 💡 Prefer readonly for ids and keys so an accidental reassignment is caught by the compiler.
- 💡 Don't over-use optional (?): if almost every field is optional, you probably need two interfaces (e.g. one for creating, one for the saved record).
- 💡 Compose with extends instead of copy-pasting fields between interfaces.
📋 Quick Reference
| Pattern | Syntax |
|---|---|
| Interface | interface User { name: string } |
| Optional property | bio?: string |
| Readonly property | readonly id: number |
| Method signature | speak(): string |
| Extend | interface Dog extends Animal {} |
| Index signature | [key: string]: string |
| Type alias / union | type ID = string | number |
| Intersection | type 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
- ✅ An interface describes the shape an object must have
- ✅ ? makes a property optional; readonly locks it after creation
- ✅ Method signatures (speak(): string) require objects to provide functions
- ✅ extends composes a bigger interface from smaller ones
- ✅ Index signatures ([key: string]: string) model flexible dictionaries
- ✅ Use type for unions, intersections, and primitives; interface for object shapes
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
- Previous: Basic Types
- Next: Functions and Generics — Type your function parameters and return values; write flexible generic functions
- Quick reference: TypeScript cheat sheet › Objects & Interfaces