Structural typing (duck typing) in TypeScript
What is Structural Typing?
TypeScript uses a structural type system β types are compatible based on their structure (shape/properties), not their name or declaration. If two types have the same structure, they are compatible.
"If it walks like a duck and quacks like a duck, it must be a duck."
Basic Example
typescript
interface Point {
x: number;
y: number;
}
// No explicit "implements Point" needed!
const point = { x: 10, y: 20, z: 30 };
function printPoint(p: Point): void {
console.log(`(${p.x}, ${p.y})`);
}
printPoint(point); // β
Works! point has x and yStructural vs Nominal Typing
| Structural (TypeScript) | Nominal (Java, C#) |
|---|---|
| Types match by structure | Types match by name/declaration |
No implements needed | Must explicitly implement interface |
| More flexible | More strict |
typescript
// In TypeScript (structural):
interface Cat { meow(): void }
interface CatLike { meow(): void }
const cat: Cat = { meow() {} };
const catLike: CatLike = cat; // β
Same structure = compatible
// In Java (nominal): this would FAIL
// Cat and CatLike are different types even with same methodsExcess Property Checking
TypeScript has special strict checking for object literals (direct assignment):
typescript
interface Config {
host: string;
port: number;
}
// β Excess property check β object literal
const config: Config = {
host: "localhost",
port: 3000,
debug: true // β Error: 'debug' does not exist in type 'Config'
};
// β
No excess check β through variable
const obj = { host: "localhost", port: 3000, debug: true };
const config: Config = obj; // β
Works (structural compatibility)
// β
No excess check β through function argument
function startServer(config: Config) {}
startServer(obj); // β
WorksClass Compatibility
typescript
class Animal {
name: string;
constructor(name: string) { this.name = name; }
}
class Person {
name: string;
constructor(name: string) { this.name = name; }
}
// Structurally compatible β same shape!
let animal: Animal = new Person("Alice"); // β
let person: Person = new Animal("Dog"); // β
When Structural Typing Causes Issues
typescript
type USD = number;
type EUR = number;
// β No type safety β both are just numbers
function convertToEUR(amount: USD): EUR {
return amount * 0.85;
}
const euros: EUR = 100;
convertToEUR(euros); // β
No error! (but logically wrong)Solution: Branded Types
typescript
type USD = number & { __brand: "USD" };
type EUR = number & { __brand: "EUR" };
function usd(amount: number): USD {
return amount as USD;
}
function convertToEUR(amount: USD): EUR {
return (amount * 0.85) as EUR;
}
const dollars = usd(100);
convertToEUR(dollars); // β
convertToEUR(100 as EUR); // β Error: EUR is not assignable to USDImportant:
Structural typing makes TypeScript flexible and compatible with JavaScript patterns. However, it means types with the same shape are interchangeable β use branded types when you need nominal-like behavior (e.g., currency, user IDs, etc.).
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.