The Case Against Static Typing
DHH's Controversial Stance on TypeScript and Type Systems
Full disclosure: This post was written by a human (me), polished by an AI (it fixed my grammar and made me sound smarter), then reviewed by me again (to make sure the AI didn't make me sound too smart). Any remaining errors are 100% organic, artisanal, human-made mistakes.
In a world where TypeScript has become the default choice for serious JavaScript development, DHH remains a vocal dissenter. In his Lex Fridman interview, he dropped a bomb: he actively refuses TypeScript in Rails projects. Not because he doesn't understand it, but because he believes dynamic typing is fundamentally superior for most applications.
The Controversial Position
"Static typing introduces repetition—you're declaring types multiple times. It prevents metaprogramming. It adds overhead. And the bugs it catches? Tests catch those too, while also catching the bugs TypeScript can't."
— DHH on Lex Fridman Podcast
This isn't ignorance—DHH has been programming for decades and understands type systems deeply. It's a principled rejection based on specific trade-offs that most developers never consider.
The Case Against Static Typing
1. Repetition and Verbosity
Static typing requires you to declare types multiple times. You declare the type when defining a function, when calling it, when storing the result. This isn't just aesthetic noise—it's cognitive overhead.
// TypeScript: Types everywhere
interface User {
id: number;
name: string;
email: string;
}
function getUser(id: number): Promise<User> {
return fetch(`/users/${id}`).then(r => r.json() as User);
}
const user: User = await getUser(1);
// Ruby: Just write the code
def get_user(id)
fetch("/users/#{id}").json
end
user = get_user(1)2. Metaprogramming Becomes Impossible
Ruby's power comes from metaprogramming—the ability to write code that writes code. Methods can be added at runtime. Objects can respond to messages dynamically. This enables domain-specific languages and elegant APIs that static typing simply cannot express.
# Ruby metaprogramming - impossible to type statically
class User < ActiveRecord::Base
# These methods don't exist until runtime!
# find_by_email, find_by_name, etc.
end
user = User.find_by_email("test@example.com")
# Or Rails' dynamic finders
Post.where(published: true).order(:created_at).limit(10)
# The type of each method chain depends on what came beforeDuck typing—where objects are defined by what they can do, not what they are—enables flexible, composable code. TypeScript's structural typing approximates this, but can't capture truly dynamic behavior.
3. Types Don't Catch the Bugs That Matter
Here's the uncomfortable truth: most bugs aren't type errors. They're logic errors, edge cases, race conditions, and integration failures. Static typing gives you confidence about the narrow slice of bugs that compilers can catch, while the important bugs slip through.
What TypeScript catches: Passing a string where a number is expected
What TypeScript misses: Off-by-one errors, null pointer exceptions (despite `strictNullChecks`), race conditions, incorrect business logic, security vulnerabilities, performance issues
Tests: The Superior Alternative
DHH's alternative to static typing isn't "hope for the best"—it's comprehensive testing. And here's his key insight: tests catch everything types catch, plus everything types can't catch.
What Good Tests Verify
- • Type correctness (by exercising the code)
- • Business logic correctness
- • Edge case handling
- • Integration between components
- • Error handling behavior
- • Performance characteristics
- • Security boundaries
If you're going to write tests anyway (and you should), the marginal benefit of static typing diminishes significantly. You're paying the cost of type annotations for bugs that your tests already catch.
The Speed Argument
"But I catch errors immediately in my IDE!" Yes, and you also spend time writing type annotations, fixing type errors, and wrestling with complex generic types. DHH argues the total time spent is often higher with static typing, not lower.
When Static Typing Does Make Sense
To be fair, DHH isn't saying static typing is always wrong. There are contexts where it genuinely helps:
- Large teams with high turnover: When developers can't hold the whole system in their heads, types serve as documentation.
- Complex data transformations: When you're shuffling data between many formats, types help track what's what.
- Library code: Public APIs benefit from explicit contracts.
- Performance-critical systems: Types enable compiler optimizations.
But for a typical web application? A CRUD app? A startup MVP? The overhead of static typing often outweighs the benefits.
Finding Your Own Balance
The typing debate isn't binary. Here's a pragmatic approach:
For New Projects
- Start with dynamic typing if using Ruby, Python, or vanilla JS
- Add types at boundaries (APIs, database schemas) if needed
- Invest heavily in testing instead
- Only add full static typing if pain points emerge
For Existing Projects
- Don't migrate to TypeScript just because it's trendy
- Evaluate whether your actual bugs would have been caught by types
- Consider the opportunity cost: what else could your team build?
For Your Career
- Learn both paradigms deeply
- Understand the trade-offs, not just the hype
- Make decisions based on your context, not Twitter consensus
The Courage to Be Contrarian
What I admire about DHH's position isn't necessarily that he's right—reasonable people can disagree on typing. It's that he's willing to think independently and defend an unpopular position with clear reasoning.
In an industry that often follows herd mentality, DHH reminds us that the "best practice" isn't always the best practice for your situation. TypeScript has genuine benefits. But it also has genuine costs. And pretending those costs don't exist because "everyone" uses TypeScript is intellectual laziness.
The question isn't "Should I use TypeScript?"
It's "What are the specific trade-offs of static typing for my project, my team, and my domain? And have I actually thought them through, or am I just following the crowd?"
Related Articles
Why Programmer Happiness Should Be Your #1 Design Metric
Matz designed Ruby with one radical idea: programmer happiness as the primary goal. Learn why this philosophy produces better code, happier teams, and more sustainable projects than languages designed around machine efficiency or corporate control.
Code as Poetry: The Aesthetics of Ruby
Why does `5.days.ago` feel so satisfying? DHH explains how Ruby's philosophy of removing 'line noise' creates code that reads like natural language. An exploration of beauty in programming.
Trusting Programmers: The Revolutionary Idea Behind Ruby
Java's sandbox approach vs. Ruby's sharp knives philosophy. Why treating developers like adults produces better code, the hidden costs of 'protecting' programmers from themselves, and building a culture of responsibility.