
Static vs Dynamic Typing
When is type decided? Strict Bureaucrat (Static) vs Flexible Startup (Dynamic).

When is type decided? Strict Bureaucrat (Static) vs Flexible Startup (Dynamic).
Why does my server crash? OS's desperate struggle to manage limited memory. War against Fragmentation.

Two ways to escape a maze. Spread out wide (BFS) or dig deep (DFS)? Who finds the shortest path?

Fast by name. Partitioning around a Pivot. Why is it the standard library choice despite O(N²) worst case?

Establishing TCP connection is expensive. Reuse it for multiple requests.

Early startup days. We built our MVP in Python because speed was everything. We needed to ship fast, get customer feedback, iterate. Python's dynamic typing let us write code without thinking twice about types.
def calculate_discount(price, discount):
return price - discount
# Works perfectly
result = calculate_discount(100, 10) # 90
The trouble started when we scaled. 3 AM phone call. "The checkout page is broken." I checked the logs and found this beauty:
# Actual production error
TypeError: unsupported operand type(s) for -: 'int' and 'str'
Someone had called calculate_discount(100, "10%") and the server crashed when it tried to subtract a string from an integer. The code looked fine during development. All our tests passed. But in production, with real user data, it exploded. Code reviews didn't catch it. Unit tests didn't catch it. Only runtime caught it, when a customer was trying to pay.
After the third incident like this, I started wondering. How often do Java teams deal with these bugs? I looked at their codebases and saw type declarations everywhere.
int price = 100;
String name = "Product";
price = "Product"; // Compile error before it even runs
The compiler checked types before execution. If you tried to assign the wrong type, it wouldn't even let the program run. Like airport security that catches weapons before boarding. Meanwhile, Python and JavaScript were like fast-track boarding with no security check. Get on the plane quickly, but if there's a problem, you discover it mid-flight.
Switching to Java wasn't an option. We had too much Python and JavaScript code already. Plus, static typing had a reputation for being verbose and slowing down development. But I was tired of 3 AM phone calls about production crashes.
Studying type systems, I finally got it. The difference between static and dynamic typing is when you check types.
Static typing determines all variable types before runtime, during compilation. You declare the type when you create a variable. If you try to assign a value that doesn't match, the compiler throws an error. You catch all type problems before execution.
// TypeScript (static typing)
function calculateDiscount(price: number, discount: number): number {
return price - discount;
}
calculateDiscount(100, "10%"); // Compile error: string not assignable to number
In VS Code, the moment I write this code, a red squiggle appears. "Type 'string' is not assignable to type 'number'." I catch the bug before execution, before deployment, even before committing the code.
Dynamic typing determines types at runtime, during execution. You don't declare types when creating variables. The program figures out the type based on the assigned value. It's flexible and fast, but type errors only surface when that specific code path executes.
# Python (dynamic typing)
def calculate_discount(price, discount):
return price - discount
# No problem during development
result = calculate_discount(100, 10)
# In production, someone calls it like this and boom
result = calculate_discount(100, "10%") # TypeError!
Think of static typing like airport security before boarding. They open your bags and remove dangerous items. Takes time, but prevents mid-flight disasters. Dynamic typing is fast-track boarding. No checks, board immediately, depart quickly. But if something goes wrong, you discover it in the air.
Type systems aren't just a binary choice between static and dynamic. There's a whole spectrum with various concepts in between.
Static typing doesn't always mean explicitly declaring types. Modern static languages support type inference. TypeScript automatically infers types from values.
// Even without explicit types, TypeScript infers them
let price = 100; // Inferred as number
let name = "Product"; // Inferred as string
price = "text"; // Error: string not assignable to number
The compiler looks at the initial value, infers the type, then enforces that type going forward. You get static type safety with dynamic type brevity.
Common in dynamic languages. "If it walks like a duck and quacks like a duck, it's a duck." You don't check the variable's type explicitly. You just verify it has the properties or methods you need.
# Duck Typing
class Duck:
def quack(self):
return "Quack!"
class Person:
def quack(self):
return "I'm imitating a duck!"
def make_it_quack(thing):
print(thing.quack()) # Don't care what 'thing' is, just needs quack()
make_it_quack(Duck()) # Quack!
make_it_quack(Person()) # I'm imitating a duck!
Flexible because you're not checking types. But pass an object without a quack method and you get a runtime error. TypeScript implements a similar concept with structural typing.
interface Quackable {
quack(): string;
}
function makeItQuack(thing: Quackable) {
console.log(thing.quack());
}
// Even without explicitly implementing Quackable, if it has quack(), it passes
makeItQuack({ quack: () => "Quack!" });
Adding static types optionally to dynamic languages. TypeScript is the poster child. You can write plain JavaScript, but if you add types, you get compile-time checks.
// Works without types (like dynamic typing)
function add(a, b) {
return a + b;
}
// Add types and get checking (like static typing)
function addTyped(a: number, b: number): number {
return a + b;
}
Python supports this too through type hints. Runtime doesn't check types, but tools like mypy enable static analysis.
# Python type hints
def calculate_discount(price: int, discount: int) -> int:
return price - discount
# mypy catches the error
calculate_discount(100, "10%") # mypy error: Argument 2 has incompatible type "str"
Discovering TypeScript while working in the JavaScript ecosystem was a game-changer. TypeScript is a superset of JavaScript with static types added. You can use existing JavaScript code as-is, but when you add types, you get compile-time checking.
// JavaScript code (dynamic typing)
function getUserData(userId) {
return fetch(`/api/users/${userId}`).then(res => res.json());
}
// Converted to TypeScript (static typing)
interface User {
id: number;
name: string;
email: string;
}
function getUserData(userId: number): Promise<User> {
return fetch(`/api/users/${userId}`).then(res => res.json());
}
// Now we catch type errors early
getUserData("123"); // Error: string not assignable to number
getUserData(123).then(user => {
console.log(user.name.toUpperCase()); // Guaranteed that name is a string
});
TypeScript's real power comes from IDE integration. Writing code in VS Code, I get autocomplete, type checking, and refactoring support in real-time. When calling a function, it automatically tells me what types of arguments to pass. Wrong type? Instant error.
// In VS Code, just typing user. shows autocomplete
user. // Auto-suggests id, name, email
// Refactoring is safe too
// Change email to emailAddress in the User interface
// Automatically finds all references
Theory learned, time for practical decision-making criteria. My personal matrix for choosing type systems based on project needs.
| Feature | Static (Java, C++, Rust) | Gradual (TypeScript, Python+mypy) | Dynamic (Python, JS) |
|---|---|---|---|
| Safety | High (compile-time guarantee) | Medium (optional checks) | Low (runtime errors) |
| Dev Speed | Slow (type declarations needed) | Medium (optional types) | Fast (no type worries) |
| Flexibility | Low (strict rules) | Medium (mix typed/untyped) | High (free changes) |
| IDE Support | Best (perfect autocomplete) | Good (typed parts only) | Limited (hard to infer) |
| Refactoring | Safe (tracks all impacts) | Okay (depends on coverage) | Risky (runtime only) |
| Learning Curve | High (learn type system) | Medium (gradual learning) | Low (start immediately) |
| Best For | Large systems, finance | Growing startups | MVP, prototypes |
I took the Python discount bug from earlier and ported it to TypeScript.
// Before: JavaScript (dynamic typing)
function calculateTotal(items) {
return items.reduce((sum, item) => {
const discount = item.discount || 0;
return sum + (item.price - discount);
}, 0);
}
// Explodes in production
const total = calculateTotal([
{ price: 100, discount: "10%" } // discount is a string!
]);
Converting to TypeScript with explicit types.
// After: TypeScript (static typing)
interface Item {
price: number;
discount?: number; // Optional, but must be number
}
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => {
const discount = item.discount || 0;
return sum + (item.price - discount);
}, 0);
}
// Compile error: discount must be number
const total = calculateTotal([
{ price: 100, discount: "10%" } // Type 'string' is not assignable to type 'number'
]);
// Correct usage
const total = calculateTotal([
{ price: 100, discount: 10 } // OK
]);
The moment I write this code, VS Code draws a red squiggle. I catch the bug before customers click, before deployment, even before running tests. This is the power of static typing.
Building an API in Python. Wrote the function, tests passed, but in production, unexpected data came in and everything broke.
# Python code (dynamic typing)
def format_price(price):
return f"${price:.2f}"
# No problem during testing
print(format_price(19.99)) # $19.99
# In production, someone calls it like this
print(format_price(None)) # TypeError: unsupported format string passed to NoneType.__format__
Never expected None values. I added defensive code, but bugs like this kept happening.
# Added defensive code
def format_price(price):
if price is None:
return "$0.00"
if not isinstance(price, (int, float)):
raise ValueError(f"Expected number, got {type(price)}")
return f"${price:.2f}"
Things improved after introducing type hints and mypy.
# Python + Type Hints
from typing import Optional
def format_price(price: Optional[float]) -> str:
if price is None:
return "$0.00"
return f"${price:.2f}"
# mypy catches type errors before runtime
format_price("19.99") # mypy error: Argument 1 has incompatible type "str"; expected "Optional[float]"
Still dynamically typed at runtime, but adding mypy to the CI pipeline means we catch type errors before deployment.
As our service grew, we needed to change the User object structure. In our JavaScript codebase, I had to manually search and update every reference.
// JavaScript (dynamic typing)
const user = {
userName: "john_doe",
userEmail: "john@example.com"
};
// References scattered throughout codebase
console.log(user.userName); // Here
sendEmail(user.userEmail); // There
validateUser(user.userName); // Everywhere
Changing userName to username meant grep searching and manually updating each location. Miss one spot? Runtime error.
After migrating to TypeScript, everything changed.
// TypeScript (static typing)
interface User {
userName: string; // Change this to username
userEmail: string;
}
const user: User = {
userName: "john_doe",
userEmail: "john@example.com"
};
// Every reference immediately shows errors
console.log(user.userName); // Property 'userName' does not exist
When I changed userName to username in the interface, red squiggles appeared across the entire codebase. I knew exactly what needed fixing, and the compiler tracked every reference. Using VS Code's "Rename Symbol" feature, I could refactor the entire codebase in one shot.
Static typing isn't universally better and dynamic typing isn't bad. Each has strengths and weaknesses. Choose based on your project situation.
Early-stage startup building an MVP fast? Python or JavaScript's dynamic typing is advantageous. Don't waste time on type declarations. Focus on business logic. When you need rapid customer feedback and frequent code changes, flexibility is a huge plus.
But when your team grows and the codebase gets complex, dynamic typing's freedom becomes a burden. Runtime errors multiply. Refactoring becomes scary. That's when to introduce static typing (or gradual typing).
My conclusion: gradual typing (TypeScript, Python+mypy) is the realistic middle ground. You don't throw away your existing codebase while gradually increasing type safety. Add types to critical parts first, handle less important parts later.
Type systems are tools. You need a hammer for nails, a screwdriver for screws. Choose the type system that fits your project stage and team situation. If you're tired of 3 AM phone calls about production crashes, seriously consider static typing.
The metaphor that stuck with me: static typing is like spell-check while you're writing an email. It catches typos before you hit send. Dynamic typing is like sending the email first, then having the recipient tell you there's a typo. Both approaches work. One just catches problems earlier.