Sostieni AppuntiFacili con una piccola donazione su PayPal
Dona con PayPalAn interface is a syntax construct that lets you describe the shape of an object: which properties it has, what type each property is, and which methods it exposes. Once you declare an interface, TypeScript will check every object you claim to be “of that interface” against that shape at compile time.
Interfaces don’t produce any runtime code — they disappear completely once your TypeScript is compiled to JavaScript. They exist purely to help the compiler (and your editor’s autocomplete) understand what a value looks like.
interface Person {
name: string;
age: number;
height?: number;
hello: () => void;
}
Here Person describes an object that must have a name (string), an age (number), an optional height (number, may be omitted), and a hello method with no parameters and no return value.
const person: Person = {
name: "Dennis",
age: 25,
hello: function () {
console.log(this.name + " say hi");
}
};
Notice that person never wrote implements Person anywhere — there’s no such keyword for plain object literals. TypeScript simply checked: “does this object have a name: string, an age: number, and a hello function? Yes → it’s a valid Person.”
This is called structural typing (sometimes nicknamed “duck typing”: if it walks like a duck and quacks like a duck…”). TypeScript doesn’t care about where a type came from or what it was declared as — it only cares about the actual shape of the value.
TIP
Structural typing is one of the biggest mental shifts coming from languages like Java or C#, where types are nominal — a class is only a Person if it explicitly says class Employee implements Person. In TypeScript, any object with a compatible shape qualifies, even if it was never declared against that interface.
The height?: number property uses the ? modifier, meaning the property may be entirely absent from the object — not just undefined, but missing. That’s exactly what happens above: person has no height field at all, and the compiler is happy with it.
// This also compiles: height is present
const tall: Person = {
name: "Marco",
age: 30,
height: 1.9,
hello() { console.log("hi"); }
};
WARNING
A common beginner mistake is assuming an optional property is always string | undefined-like and safe to use directly. If you read person.height without checking, TypeScript correctly infers its type as number | undefined, and using it in arithmetic without a guard (person.height + 1) will be flagged as an error, since undefined can’t be added to a number.
The hello: () => void line describes a method using arrow-function syntax to define its type: no parameters, returns nothing (void). You could also write it with the shorthand method syntax, which behaves identically for type-checking purposes:
interface Person {
name: string;
age: number;
hello(): void; // equivalent to hello: () => void
}
Both forms are structurally the same. The arrow-style is often preferred because it composes more naturally with optional methods (hello?: () => void) and matches how you’d type a standalone function variable.
An interface can build on top of another one using extends. The new interface inherits every property from the base interface and can add more of its own.
interface Employee extends Person {
employeeId: number;
}
const worker: Employee = {
name: "Dennis",
age: 25,
height: 1.76,
employeeId: 10,
hello: function () {
console.log(this.name + " say hi");
}
};
worker must satisfy all the fields from Person and the new employeeId field — extends is purely additive.
An interface can extend more than one interface at once, separated by commas. This lets you compose several shapes together:
interface Manager extends Employee, Person {
employees: Person[];
}
const manager: Manager = {
name: "Dennis",
age: 25,
height: 1.76,
employeeId: 10,
hello: function () {
console.log(this.name + " say hi");
},
employees: [worker, person]
};
Since Employee already extends Person, adding Person again here is redundant (it’s already inherited transitively) — but TypeScript allows it as long as the fields don’t conflict, which makes this a harmless — if slightly unusual — way of being explicit about a type’s full ancestry.
classDiagram
class Person {
+string name
+number age
+height? number
+hello() void
}
class Employee {
+number employeeId
}
class Manager {
+Person[] employees
}
Person <|-- Employee
Employee <|-- Manager
Person <|-- Manager
interface vs type aliasesTypeScript also lets you describe object shapes with a type alias:
type PersonType = {
name: string;
age: number;
};
For plain object shapes, interface and type are largely interchangeable, but there are two notable differences worth knowing now (we’ll go deeper in the Type Aliases lesson):
interface twice in the same scope, TypeScript merges the two declarations into one combined shape. A type alias can never be redeclared — doing so is a compile error.extends; type aliases compose via intersections (type Manager = Employee & Person & { employees: Person[] }). Only type can alias things an interface fundamentally cannot express, such as unions (type ID = string | number).INFO
A common rule of thumb in the TypeScript community: use interface for public object shapes that might need to be extended or merged (e.g. library APIs), and type for everything else — unions, tuples, function signatures, and one-off shapes.
What does TypeScript check when you assign an object literal to a variable typed with an interface?
What does the `?` in `height?: number` mean?
What is the name for TypeScript's approach of matching object shapes instead of explicit type declarations?
What does `interface Manager extends Employee, Person { ... }` do?
Which of these can a `type` alias do that a plain `interface` cannot?
What happens if you declare the same `interface` twice in the same scope?
Scenario: You’re building a small library catalog and need to model books and their authors using interfaces.
Task:
Author with name: string and an optional bio?: string.Book with title: string, pages: number, and author: Author.EBook extends Book that adds fileSizeMb: number and a method download(): void.Learning objective: practice defining nested interfaces, optional properties, and extending an interface with additional members.
Scenario: Your team wants to enforce a consistent shape for API error responses across a project, and also allow interfaces to merge shapes together.
Task:
ApiError with code: number and message: string.ValidationError extends ApiError that adds field: string.logError(error: ApiError): void that accepts any object with a compatible shape, not just objects explicitly typed as ApiError — call it with a plain object literal to prove structural typing works.ApiError.Learning objective: understand how structural typing lets unrelated object literals satisfy the same interface, and how interface extension builds specialized error shapes.
Prenota una lezione