So far we’ve seen primitive types (string, number, boolean). But the real world runs on objects.
Users, Products, Orders… they are all objects with multiple properties.
The Anarchy of Anonymous Objects
In pure JavaScript, we often pass objects around trusting our memory.
function printUser(user) {
// 🤞 Let's hope 'user' has these properties...
console.log("Name: " + user.name.toUpperCase());
console.log("Age: " + user.age);
}
// A month later, someone calls the function like this:
printUser({ nombre: "Alice", edad: 30 });
// 💥 CRASH: Cannot read properties of undefined (reading 'toUpperCase')
// Why? Because we passed "nombre" (Spanish) but the function expected "name" (English).function printUser(user) {
// 🤞 Let's hope 'user' has these properties...
console.log("Name: " + user.name.toUpperCase());
console.log("Age: " + user.age);
}
// A month later, someone calls the function like this:
printUser({ nombre: "Alice", edad: 30 });
// 💥 CRASH: Cannot read properties of undefined (reading 'toUpperCase')
// Why? Because we passed "nombre" (Spanish) but the function expected "name" (English).TypeScript prevents this by forcing you to define the SHAPE of your objects.
Interfaces: The Contract
An interface is like a contract. If an object claims to be a User, it MUST fulfill the contract.
interface User {
name: string;
age: number;
isDeveloper: boolean;
}
const alice: User = {
name: "Alice",
age: 39,
isDeveloper: true
}; // ✅ Fulfills the contract
const pepe: User = {
name: "Pepe"
}; // ❌ Error: Missing properties 'age' and 'isDeveloper'interface User {
name: string;
age: number;
isDeveloper: boolean;
}
const alice: User = {
name: "Alice",
age: 39,
isDeveloper: true
}; // ✅ Fulfills the contract
const pepe: User = {
name: "Pepe"
}; // ❌ Error: Missing properties 'age' and 'isDeveloper'Optional Properties (?)
Sometimes we don’t have all the data. Maybe the email is optional.
For that, we use the question mark ? after the property name.
interface Product {
id: number;
name: string;
description?: string; // 👈 Optional (string | undefined)
}
const desk: Product = {
id: 1,
name: "Desk"
// No need to include description
};interface Product {
id: number;
name: string;
description?: string; // 👈 Optional (string | undefined)
}
const desk: Product = {
id: 1,
name: "Desk"
// No need to include description
};Read-only Properties (readonly)
If you want to ensure that a property never changes after creating the object (like an ID), use readonly.
interface Configuration {
readonly apiKey: string;
theme: "light" | "dark";
}
const config: Configuration = {
apiKey: "xyz-123",
theme: "light"
};
config.theme = "dark"; // ✅ You can change this
config.apiKey = "abc-999"; // ❌ Error: Cannot assign to 'apiKey' because it is a read-only property.interface Configuration {
readonly apiKey: string;
theme: "light" | "dark";
}
const config: Configuration = {
apiKey: "xyz-123",
theme: "light"
};
config.theme = "dark"; // ✅ You can change this
config.apiKey = "abc-999"; // ❌ Error: Cannot assign to 'apiKey' because it is a read-only property.type vs interface?
You’ll see people using type User = { ... } instead of interface User { ... }.
To define the shape of an object, both work almost the same.
Golden Rule (for now):
- Use
interfaceto define objects that represent entities (User, Post, Product).- Use
typefor Unions (string | number), primitives, or tuples.
In Part 8, we will dive deeper into technical differences, but for now, stick with this.
Try it yourself!
Let’s put what you’ve learned to the test. You need to define an interface and create a specific object to pass the hidden validation.
Exercise Requirements:
- Define an interface called
VideoGamewith:title(string)year(number)platform(optional, string)
- Create a constant object called
favoriteGamethat implements this interface. - The data must be exactly: “The Legend of Zelda”, year 1986.
In Part 4, we’ll see what happens when we have many identical objects… that is, Arrays.