I’ve spent years inside languages that market themselves as “type safe.” Rust, TypeScript, C#—you name it. And I’ve spent just as much time in the ones that don’t bother with the sales pitch: Python, JavaScript, a healthy dose of Bash. After a while, a quiet suspicion starts to form. That warm blanket of safety the type system wraps around you? It’s not really about safety. It’s about convenience. The compiler isn’t guarding you against bad logic. It’s just doing your bookkeeping.
This isn’t a manifesto against types. I write TypeScript almost every day. I like it. But I’ve learned to separate the feeling of safety from the real thing. The compiler catches when you miss a property or fat-finger a function name. That’s handy. It is not, however, the same as preventing the kind of bug that wakes you up at 3 a.m. If you confuse the two, you’ll build a system that compiles beautifully and fails quietly in production.
The Bookkeeping Problem
Most “type errors” are just mismatched shapes. You said an object would have a name, but you passed it a title. The compiler flags it. You fix it in ten seconds. That’s not safety—that’s a memory aid. A really good one, sure. But let’s not dress it up as something profound.
Think about the last production incident you actually lost sleep over. Was it a shape mismatch? Probably not. The nasty bugs are the ones where the shape is perfect but the values are nonsense. A negative price. A timestamp in UTC when the rest of the system expects Eastern. An email address that passes every regex check but bounces because the domain doesn’t exist. The type system waves those through with a smile. It checked the shape. The rest is your problem.
What we call type safety is really automated documentation that yells at you. The compiler reads your annotations and tells you when you’ve contradicted yourself. That’s useful. But it’s not safety. It’s a spellchecker for data structures.
The Comfort Trap
Here’s where it gets risky. When you believe the type system has your back, you stop writing the checks that actually matter. I’ve watched teams strip out runtime validation after a TypeScript migration. “The types handle that now.” They don’t. Types handle structural conformance. They don’t know anything about your business rules.
Take a payment function. The type says the amount is a number. The compiler makes sure you don’t accidentally shove a string in there. Good. But does it check that the number is positive? That it doesn’t exceed the available balance? That it’s not zero? Of course not. Those are domain invariants. They live outside the type system. If you deleted your validation layer because you trusted the types, you didn’t make your system safer. You made it more fragile.
I’ve seen this pattern enough times to recognize it instantly. A team moves from JavaScript to TypeScript and the unit tests for data validation quietly disappear. “The types cover it.” No. They cover structure. They don’t cover the fact that an age should be between 0 and 150, or that a SKU has to match a specific pattern, or that a date range needs the start before the end. You can encode some of those in the type system if you go deep enough—dependent types, refinement types, all that—but most teams don’t. And even when they do, the types vanish at runtime. They’re a compile-time hallucination.
Where Types Earn Their Keep
I’m not anti-type. I’m anti-cargo-cult. Adding types to a codebase doesn’t make it safe. It makes it ergonomic. Autocomplete. Safe refactoring without a grep nightmare. Catching the dumb mistakes that would otherwise cost you five minutes and a coffee refill. That’s real value. But it’s convenience value, not safety value.
Safety comes from somewhere else. It comes from knowing your domain well enough to identify the invariants that actually matter. It comes from tests that exercise those invariants. It comes from designing systems where failures are contained and observable. A type system can help with some of that, but it’s a supporting actor, not the lead.
I’ve worked on Python codebases that were more reliable than TypeScript codebases I’ve seen. Not because Python is a better language—it’s not. But the team had a culture of testing and defensive programming. They didn’t assume the language would save them. They assumed things would break and built accordingly. That mindset matters more than the type checker.
The Runtime Gap
Here’s the part nobody talks about enough: types are a compile-time fiction. When your code actually runs, it’s just JavaScript or assembly or bytecode. The types are gone. If you’re consuming data from an API, a database, or user input, the types you declared in your source code mean exactly nothing. The JSON blob coming over the wire doesn’t care that you called it an interface User. It’s just bytes with an attitude.
This is why I get twitchy when I see codebases that rely entirely on TypeScript for data validation. Unless you’re generating runtime validators from your types—which is possible, but not the default—you’re trusting external data to match your internal assumptions. That’s not safety. That’s wishful thinking.
I’ve been burned by this more times than I’d like to admit. An API changes a field from string to number. Your TypeScript compiles clean because you updated the interface. But the old clients are still sending strings, and your server happily accepts them because at runtime, it’s all just JavaScript. The type system didn’t save you. It gave you a comforting lie.
What Real Safety Looks Like
Real safety is boring. It’s validation at every system boundary. It’s parsing, not casting. It’s code that fails loudly and early when assumptions break. It’s monitoring and alerting. It’s designing your system so one bad input doesn’t corrupt your entire database.
Types can help with this. A good type system makes it easier to express your assumptions and catch inconsistencies. But it’s one tool in the toolbox. If you treat it as the whole workshop, you’re going to build something that looks great on the shelf and collapses the first time someone leans on it.
I’ve started thinking of types as documentation that happens to be machine-checkable. That’s genuinely powerful. Documentation that can’t drift out of sync with the code is a gift. But documentation doesn’t prevent errors. It just helps you understand what the code is supposed to do. Preventing errors requires a different mindset—one that assumes things will go wrong and plans accordingly.
Frequently Asked Questions
Doesn’t a strong type system prevent entire categories of bugs?
It prevents certain structural bugs—passing the wrong number of arguments, accessing properties that don’t exist. But the bugs that actually hurt you—logic errors, incorrect business rules, race conditions—are invisible to the type system. Calling it “safety” overstates what it does.
Should I stop using TypeScript or typed languages?
No. The convenience benefits are real and significant. Autocomplete alone saves hours of development time. The key is to understand what you’re getting: better tooling and faster feedback on structural mistakes, not a guarantee of correctness. Keep your runtime validation. Keep your tests. Don’t let the types make you complacent.
How do I know if I’m over-relying on types?
Look at your test coverage. If you’ve stopped testing input validation, edge cases, or business logic because “the types handle it,” you’re over-relying. Also, check your API boundaries. If you’re not validating data coming from external sources, you’re trusting the type system to do a job it can’t do at runtime.
What’s the difference between type safety and type convenience in practice?
Type convenience is when the compiler tells you that you forgot a property on an object. That saves you a few minutes of debugging. Type safety is when the system prevents invalid data from corrupting your application state. The first is about developer experience. The second is about system reliability. They overlap, but they’re not the same thing.


