Interfaces
- #typescript
- #interfaces
- #object-types
- #extends
In questa lezione
1. Introduction
An 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.
2. Implementing an interface with a plain object
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.
Suggerimento
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.
2.1 Optional properties
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"); }
};
Attenzione
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.
3. Method signatures
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.
4. Extending interfaces
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.
4.1 Extending multiple interfaces
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
5. interface vs type aliases
TypeScript 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):
- Declaration merging: if you declare the same
interfacetwice in the same scope, TypeScript merges the two declarations into one combined shape. Atypealias can never be redeclared — doing so is a compile error. - Composition style: interfaces compose via
extends; type aliases compose via intersections (type Manager = Employee & Person & { employees: Person[] }). Onlytypecan alias things an interface fundamentally cannot express, such as unions (type ID = string | number).
Nota
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.
6. Further reading
- TypeScript Handbook — Object Types
- TypeScript Handbook — Everyday Types (interfaces section)
- TypeScript Playground
7. Quiz
Mettiti alla prova
0/6 risposte
What does TypeScript check when you assign an object literal to a variable typed with an interface?
What does the
?inheight?: numbermean?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
typealias do that a plaininterfacecannot?What happens if you declare the same
interfacetwice in the same scope?
8. Exercises
8.1 Exercise
Scenario: You’re building a small library catalog and need to model books and their authors using interfaces.
Task:
- Create an interface
Authorwithname: stringand an optionalbio?: string. - Create an interface
Bookwithtitle: string,pages: number, andauthor: Author. - Create an interface
EBook extends Bookthat addsfileSizeMb: numberand a methoddownload(): void. - Create at least one object literal for each interface and log a formatted description of each to the console.
Learning objective: practice defining nested interfaces, optional properties, and extending an interface with additional members.
8.2 Exercise
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:
- Define an interface
ApiErrorwithcode: numberandmessage: string. - Define an interface
ValidationError extends ApiErrorthat addsfield: string. - Write a function
logError(error: ApiError): voidthat accepts any object with a compatible shape, not just objects explicitly typed asApiError— call it with a plain object literal to prove structural typing works. - Explain (in a comment) why the function accepts objects that were never declared as
ApiError.
Learning objective: understand how structural typing lets unrelated object literals satisfy the same interface, and how interface extension builds specialized error shapes.