Skip to content

Duck Typing & EAFP

TypeScript and Python are both structurally typed — a value is compatible with a type if it has the right shape. But the mechanism is completely different.

TypeScript verifies structure at compile time using interfaces and type aliases. If a value does not satisfy the interface, the compiler rejects it before the code runs. Python checks structure at runtime by simply trying to call the method or access the attribute. If the object has it, great. If not, you get an AttributeError.

This runtime approach is called duck typing: if it walks like a duck and quacks like a duck, it is a duck. Python does not care about declared types — only about whether the object can do what you ask of it.

TypeScript
// TypeScript: explicit interface required
interface Speaker {
speak(): string;
}
class Dog implements Speaker {
speak(): string { return "Woof!"; }
}
class Cat implements Speaker {
speak(): string { return "Meow!"; }
}
// TypeScript verifies the interface at compile time
function makeSound(animal: Speaker): void {
console.log(animal.speak());
}
makeSound(new Dog());
makeSound(new Cat());
Python
# Python: no interface needed — just call the method
class Dog:
def speak(self) -> str:
return "Woof!"
class Cat:
def speak(self) -> str:
return "Meow!"
class Duck:
def speak(self) -> str:
return "Quack!"
# Python checks at runtime whether .speak() exists
def make_sound(animal) -> None:
print(animal.speak())
make_sound(Dog())
make_sound(Cat())
make_sound(Duck()) # works — Duck was never declared as Speaker

In TypeScript, Duck would need to explicitly implement Speaker. In Python, any object with a .speak() method works — no declaration required.

The structural difference extends to error handling. TypeScript developers tend to use Look Before You Leap (LBYL): check whether an operation is safe before attempting it. Python culture favors Easier to Ask Forgiveness than Permission (EAFP): just try it, and handle the exception if it fails.

TypeScript
// TypeScript: LBYL — check first, then act
function getValue(obj: Record<string, unknown>, key: string): unknown {
if (key in obj) {
return obj[key];
}
return undefined;
}
// Also common in TypeScript
function parseNumber(s: string): number | null {
const n = Number(s);
return isNaN(n) ? null : n;
}
Python
# Python: EAFP — try it and handle failure
def get_value(d: dict, key: str):
try:
return d[key]
except KeyError:
return None
# LBYL equivalent works too, but is less idiomatic
def get_value_lbyl(d: dict, key: str):
if key in d:
return d[key]
return None
# EAFP for type conversion
def parse_number(s: str):
try:
return int(s)
except ValueError:
return None
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Duck:
def speak(self):
return "Quack!"
def make_sound(animal):
print(animal.speak())
for animal in [Dog(), Cat(), Duck()]:
make_sound(animal)
# EAFP pattern
def get_value(d, key):
try:
return d[key]
except KeyError:
return "not found"
data = {"name": "Alice", "age": 30}
print(get_value(data, "name"))
print(get_value(data, "email"))
What is "duck typing" in Python?
What does EAFP stand for?
In Python, if you call `.speak()` on an object that does not have that method, what happens?
Which pattern does Python culture prefer for accessing a dictionary key that might not exist?