Interfaces & Type Aliases
In JavaScript, an object is whatever shape you decide to give it, and nothing checks that
shape against anything. Pass the wrong object to a function and JavaScript won't complain until
it tries to read a property that isn't there — usually as undefined quietly
poisoning something three function calls downstream. TypeScript lets you name
and enforce an object's shape before any of that happens.
💡 The Bottom Line
An interface (or a type alias) describes what properties an object
must have and what type each one is. Once a function parameter is annotated with that shape,
TypeScript checks every call site against it — passing an object missing a required property,
or with a property of the wrong type, is a compile error, not a runtime surprise.
Describing an object shape
function printUser(user) {
console.log(user.name, user.age);
}
printUser({ name: "Ada" });
// runs fine, logs: Ada undefined
interface User {
name: string;
age: number;
}
function printUser(user: User) {
console.log(user.name, user.age);
}
printUser({ name: "Ada" });
// compile error: missing property 'age'
📋 Key differences
interface User { ... }declares a named shape once, reusable across many function signatures.- Each property line is
name: Type;— no commas between properties, just semicolons (or newlines). - The JavaScript version silently produces
undefinedfor a missing field; the TypeScript version refuses to compile the call in the first place. - The object literal itself doesn't need to say it's a
User— TypeScript checks its shape against the parameter type automatically.
interface vs type — pick one, mostly by convention
TypeScript actually gives you two ways to name this shape: interface and
type. For describing plain object shapes, they're close enough to interchangeable
that most style guides just pick one and move on.
interface User {
name: string;
age: number;
}
type User = {
name: string;
age: number;
};
interface can be re-opened later to add more properties (declaration merging)
and reads slightly more like a class contract, which is why it's the common default for object
shapes. type is required for things an interface can't express — unions like
string | number (next lesson), tuples, or aliasing a primitive. A common rule of
thumb: reach for interface for object shapes, type for everything
else.
Optional properties
Not every field is always present — a user might not have a nickname yet. Mark
a property optional with a ? right after its name, and TypeScript will allow the
object with or without it, while still catching typos on the properties that are
present.
interface User {
name: string;
nickname: string;
}
// { name: "Ada" } is now an error
interface User {
name: string;
nickname?: string;
}
// { name: "Ada" } compiles fine
// user.nickname is `string | undefined`
⚠️ Gotcha
An optional property's type isn't just its declared type — it's that type |
undefined under the hood. Code that reads user.nickname.toUpperCase()
without checking for undefined first will be flagged by TypeScript, and rightly
so: that's exactly the "cannot read property of undefined" crash JavaScript developers are
used to hitting at runtime, except now it's caught before you ship it.
Structural typing: TypeScript checks shape, not name
TypeScript's type system is structural — often described as "duck typing,
but checked at compile time instead of discovered at runtime." An object doesn't need to say it
implements User; if it has all the required properties with compatible types,
TypeScript accepts it. This is different from languages with nominal typing, where a class must
explicitly declare which interfaces it implements.
function printUser(user) {
console.log(user.name);
}
// any object with or without .name
// is accepted; failures happen at
// runtime when .name is read
interface User { name: string; }
function printUser(user: User) {
console.log(user.name);
}
const admin = { name: "Ada", role: "admin" };
printUser(admin); // fine — shape matches,
// extra properties are OK here
🔧 Try It Yourself
Define an interface Product with id: number,
name: string, and an optional discount?: number. Write a function
describe(product: Product) that builds a sentence from those fields. Call it once
with all three properties, once without discount, and once deliberately missing
name — confirm the compiler only complains about the third call.
🙋 There Are No Dumb Questions
Q: Since it's just checking shape, can I pass an object with extra properties that
aren't in the interface?
A: Usually yes, as shown above — this is called "excess property" tolerance for values stored
in a variable first. The one exception: passing an object literal directly at the
call site (not through a variable) triggers a stricter "excess property check," which flags
typos like passing { name: "Ada", agee: 30 } straight into a call — a deliberate
exception designed to catch misspelled property names.
✅ Quick Check
You define interface User { name: string; age?: number; }. Which call
compiles without error?
Next up: interfaces describe one fixed shape. To write a function or class that works safely across many shapes without losing type information, you need generics.