🚀 The Power of Interfaces in TypeScript: Writing Clear, Safe, and Scalable Code
We’ve all been there: a growing codebase, objects flying around between components, and suddenly you’re debugging a “cannot read property of undefined” at 2 AM. 😵💫
It’s not always because the code is bad — it’s often because the contracts between parts of the system are unclear.
That’s exactly where TypeScript interfaces shine. They make contracts explicit, code self-documented, and teamwork is 10x smoother.
Today, TypeScript is the de facto standard for building scalable, maintainable applications. But here’s the thing: many developers still don’t take full advantage of one of its biggest strengths — strong typing with interfaces.
In this post, I’ll share:
✔ Why interfaces are so powerful
✔ How to avoid any and use unknown safely
✔ Tricks like extending, inheriting, and removing properties with Omit
🔹 Why Interfaces Matter
✅ Define clear contracts between different parts of the code
✅ Reuse shapes across multiple functions and classes
✅ Scale in large teams (everyone knows what data is expected)
✅ Catch errors early thanks to IDE autocompletion and validation
Here’s an User interface showing all the different TypeScript property types:
interface User {
// Primitives
id: number;
name: string;
email?: string; // optional property
isActive: boolean; // true/false
// Literal types
role: "admin" | "editor" | "viewer"; // restricted literal values
// Array
tags: string[];
// Tuple
coordinates: [number, number];
// Object (nested)
address: {
street: string;
city: string;
zipCode?: number;
};
// Function type
greet: (message: string) => void;
// Index signature (flexible key/value)
[key: string]: string | number | boolean | object | undefined;
}
This interface demonstrates optional properties, literal types, arrays, tuples, nested objects, functions, and even index signatures for dynamic keys.
🔹 Why avoid any?
Using any it is is dangerous because it removes all type safety and allows errors to appear only at runtime:
let user: any = "Hello";
user.id; // No compile error, runtime crash ❌
🔹 any vs unknown
Both any and unknown allow handling values whose type is not yet known, but behave differently:
Recommended by LinkedIn
❌ any (dangerous flexibility)
✅ unknown (safer alternative)
let data: unknown = "hello";
// Compile-time error if used directly
// data.toFixed(); ❌
// Safe usage after type narrowing
if (typeof data === "string") {
console.log(data.toUpperCase()); // ✅ Works
}
Rule of thumb: use unknown instead of any whenever possible.
🔹 Extending and Inheriting Interfaces
Interfaces can extend other interfaces, allowing you to reuse and add properties without duplicating code. This is especially useful in large systems.
interface Product {
id: number;
name: string;
price: number;
}
// Extend Product for a Book
interface Book extends Product {
author: string;
pages: number;
}
// Another extension for a DigitalBook
interface DigitalBook extends Book {
fileSizeMB: number;
format: "pdf" | "epub" | "mobi";
}
🔹 Removing Properties with Omit
Instead of duplicating an interface just to remove a property, use TypeScript’s Omit utility:
interface User {
id: number;
name: string;
email: string;
password: string;
}
// Create a version without password
type PublicUser = Omit<User, "password">;
const publicUser: PublicUser = {
id: 1,
name: "Alice",
email: "alice@example.com"
// password is not allowed here
};
🔹 Conclusion
TypeScript interfaces help you write clear, safe, and scalable code. Key takeaways:
Mastering interfaces, inheritance, conditional types, and utility types like Omit will make your TypeScript codebase safer, cleaner, and more maintainable.
💡 What about you?
➡ How do you use interfaces in your daily TypeScript work?
➡ Do you have other tricks for keeping code safe and readable?
Buen artículo Borja LLorente Capiscol