🚀 Executive Summary
TL;DR: TypeScript’s as keyword (type assertions) can lead to silent runtime errors by overriding the compiler’s type inference without validating data shape. This article presents safer alternatives like conditional checks and type guards, culminating in the new Biome noAsAssertion plugin to enforce these practices and prevent as assertions across a codebase.
🎯 Key Takeaways
- Type assertions (
askeyword) bypass TypeScript’s type checking, creating a ‘lie’ to the compiler that can result in runtimeTypeErrorif the actual data shape differs. - Type guards provide a robust, reusable, and type-safe mechanism to validate an object’s shape at runtime, allowing TypeScript to correctly infer types within conditional blocks without assertions.
- The Biome
noAsAssertionplugin offers a ‘nuclear’ option to enforce the prohibition ofasassertions across a project, integrating into CI/CD to prevent future introduction of this code smell.
Tired of TypeScript’s as keyword causing runtime errors? Learn why type assertions are a code smell and how to enforce safer alternatives across your team using practices like type guards and the new Biome noAsAssertion plugin.
That TypeScript as Keyword Is a Landmine. Let’s Defuse It.
I still remember the pager going off at 3 AM. A deployment from the day before had silently corrupted user session data on prod-auth-svc-04. The culprit? A single line of code, buried deep in a utility function, that looked something like const user = sessionData as User;. The developer had “pinky promised” the TypeScript compiler that sessionData would always be a User object. But a rare edge case sent a null value down the pipe, and at runtime, our code choked. That tiny as keyword cost us a morning of frantic debugging and a whole lot of user trust. It’s a lie waiting to be exposed, and I’ve had enough of it.
The “Why”: You’re Telling, Not Asking
Before we jump into the fixes, let’s get one thing straight. The core problem with type assertions (using as or the older `` syntax) is that you are overriding the compiler’s own type inference. You are essentially telling TypeScript, “Hey, shut up, I know better than you.”
And sometimes you do! But 99% of the time, you’re just kicking a potential runtime error down the road. You’re not validating the data’s shape; you’re just forcing a label onto it. When the runtime reality doesn’t match the label you’ve slapped on, you get a classic TypeError: Cannot read properties of undefined. It’s a guarantee for future headaches.
The Solutions: From Band-Aid to Body Armor
Okay, you’re convinced. You want to stop the bleeding. Here are three ways to handle this, from a quick patch to a permanent, team-wide solution.
1. The Quick Fix: A Conditional Check
Let’s say you’re in a hurry. The pull request is due, and you just need to make the code safer without a major refactor. The simplest thing you can do is wrap your logic in a good old-fashioned if check before you use the object.
Instead of this brittle assertion:
`
function getUsername(data: unknown): string {
const user = data as { name: string };
return user.name; // This will crash if user is null or not an object!
}
`
Do this instead:
`
function getUsername(data: unknown): string | undefined {
if (data && typeof data === 'object' && 'name' in data) {
// Inside this block, TypeScript is now smart enough to know the shape!
const user = data as { name: string }; // The assertion is now much safer
return user.name;
}
return undefined; // Handle the failure case gracefully
}
`
Darian’s Take: This is still using
as, but it’s a “safe” assertion. You’ve proven the shape of the object to yourself and the compiler before you make the claim. It’s an acceptable compromise in legacy code, but for new features, we can do better.
2. The Permanent Fix: The Type Guard
This is the solution I push my team to use. A type guard is a function whose return type is a special “type predicate.” It’s a reusable, clean, and explicit way to validate an object’s shape. It’s the TypeScript equivalent of “show, don’t tell.”
Let’s define our User type and a type guard for it:
`
interface User {
id: number;
name: string;
email: string;
}
// This is the type guard function!
function isUser(obj: any): obj is User {
return (
obj &&
typeof obj.id === 'number' &&
typeof obj.name === 'string' &&
typeof obj.email === 'string'
);
}
// Now let's use it
function processUserData(data: unknown) {
if (isUser(data)) {
// No 'as' needed! TypeScript knows data is a User here.
console.log(Processing user: ${data.name.toUpperCase()});
} else {
console.error("Invalid data received. Not a user object.");
}
}
`
This is beautiful. It’s self-documenting, reusable, and completely type-safe at runtime. No more lies to the compiler.
3. The ‘Nuclear’ Option: Enforce It Everywhere with Biome
Okay, you’ve written a dozen type guards, you’ve evangelized them in PR reviews, but new as assertions keep popping up. It’s time to stop asking nicely. This is where tooling comes in. There’s a new Biome plugin on the scene, no-as-assertion, that makes using as a linting error across your entire codebase.
It’s the ultimate way to enforce best practices and train your team to think in terms of type safety first. You simply add it to your biome.json configuration.
Here’s how you set it up:
`
{
"linter": {
"enabled": true,
"rules": {
"suspicious": {
"noAsAssertion": "error"
}
}
}
}
`
Once that’s in place, your CI/CD pipeline will fail any pull request that tries to introduce a new as assertion. It forces the developer to stop and think, “How can I *prove* this type instead of just asserting it?” It forces them to write a type guard or a proper validation check.
Warning: Dropping this into a large, existing codebase can be painful. You’ll likely have to fix dozens, if not hundreds, of existing assertions. I’d recommend enabling it as a
”warn”first, cleaning up the existing tech debt, and then switching it to”error”to prevent backsliding.
Summary: Choose Your Weapon
Let’s break it down.
| Solution | Effort | Safety | Best For |
|---|---|---|---|
| 1. Conditional Check | Low | Medium | Quick patches in legacy files or hotfixes. |
| 2. Type Guard Function | Medium | High | All new features and any code that deals with external data (APIs, etc.). The gold standard. |
| 3. Biome Plugin Rule | High (initially) | Maximum | Enforcing team-wide standards and preventing future tech debt. |
Stop telling the compiler what to believe and start proving your types are what you say they are. Your future self, awake and resting peacefully at 3 AM, will thank you for it.
👉 Read the original article on TechResolve.blog
☕ Support my work
If this article helped you, you can buy me a coffee:

Top comments (0)