shouldNeverHappen: A Simple Trick for Catching the Impossible in TypeScript
In every codebase, there are situations that should just never happen. Maybe it's a value that should have been validated earlier, or a branch in your logic that you’re 100% sure will never be executed. And yet… bugs happen.
Instead of scattering throw new Error(...)
statements throughout your code, it’s better to encapsulate that logic with a dedicated utility function: shouldNeverHappen
.
Let’s talk about why this pattern is useful, how to use it properly, and a few important considerations you might not have thought of.
💡 What is shouldNeverHappen
?
It’s a small helper function that you can call when you hit a panic-level error—a defect that indicates your assumptions are broken.
Here’s a basic version:
export const shouldNeverHappen = (msg?: string, ...args: any[]): never => {
console.error(msg, ...args);
if (isDevEnv()) {
debugger;
}
throw new Error(`This should never happen: ${msg}`);
};
This is designed to fail loudly, immediately, and in developer-friendly ways:
- Logs to the console
- Breaks into the debugger when in development mode
- Throws an error to stop execution
✅ Why You Should Use It
1. Improves Readability
Using shouldNeverHappen('Missing user')
is way more expressive than a raw throw
statement. Anyone reading the code knows this is an unexpected failure.
2. Centralized Panic Handling
If you ever want to add logging, analytics, or alerting, you only need to do it in one place. This is great for production observability (think: Sentry, Datadog, etc.).
3. Enforces TypeScript’s never
Returning never
helps TypeScript understand that this function does not return, allowing better control flow analysis and eliminating the need for !
non-null assertions.
4. Developer Experience Boost
During development, opening the debugger immediately saves you from guessing where things went wrong. It's like a smart breakpoint.
🧪 Example Usage
const user = maybeUser ?? shouldNeverHappen('User expected but missing');
You expect maybeUser
to exist, and if it doesn’t, your assumption is broken—fail fast.
if (!config.allowUpload) {
return shouldNeverHappen('Uploads should always be allowed here');
}
This pattern helps ensure your app never silently continues in a bad state.
🧠 Extra Considerations
✅ Only Use for Defects
Don’t use shouldNeverHappen
for user input errors, API failures, or things that are expected to go wrong occasionally. This is for true internal logic failures, not recoverable runtime issues.
📦 Add Logging/Telemetry Hooks
Consider extending the utility to report to tools like Sentry:
import * as Sentry from '@sentry/node';
export const shouldNeverHappen = (msg?: string, ...args: any[]): never => {
console.error(msg, ...args);
Sentry.captureException(new Error(msg));
if (isDevEnv()) debugger;
throw new Error(`This should never happen: ${msg}`);
};
🌐 Works Great with Feature Flags
If you're doing gradual rollouts or A/B testing, shouldNeverHappen
can act as a guardrail for assumptions that should never be broken after rollout.
🚨 Avoid in Library Code
This pattern is great for internal applications, but in published libraries, you should generally use standard error types and let consumers decide how to handle them.
🧵 Bonus: Add Context to the Error
You can pass any number of arguments to provide more context:
shouldNeverHappen('Unexpected null in payment flow', { transactionId, user });
This makes debugging way easier later.
🧼 Clean, Simple, Powerful
The real strength of shouldNeverHappen
is that it keeps your codebase clean, consistent, and intentional. It separates real bugs from expected failures, and it does so in a way that improves both the developer experience and the runtime safety of your application.
If you’re not using something like this yet—you should start today.
Comments ()