This article explains a short interview answer in simple terms.
It is a simplified explanation for developers who already know the basic TypeScript theory, but want to better understand how it behaves in real projects. Some details are intentionally left out to keep the focus practical rather than academic.
*Take the quiz: Common TypeScript Anti-Patterns
When TypeScript stops protecting you.
1. Overusing any
any completely disables type checking. From TypeScript’s point of view, the code becomes plain JavaScript.
let user: any = getUser();
user.profile.avatar.toUpperCase();
The compiler is silent, but at runtime this can break if profile or avatar is missing.
Why this is a problem
- Type safety is lost
- Bugs move from compile time to runtime
- IDE autocompletion and hints disappear
- Errors often appear only in production, not during development
When it can be acceptable
- During gradual migration from JavaScript
- In very small, isolated integration points
A safer alternative: unknown
let user: unknown = getUser();
if (typeof user === 'object' && user !== null) {
// still not enough to safely access nested properties
}
This check alone is not sufficient. To safely work with nested fields, you need a structural check.
type User = {
profile?: {
avatar?: string;
};
};
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'profile' in value
);
}
if (isUser(user)) {
const avatar = user.profile?.avatar;
if (avatar) {
avatar.toUpperCase();
}
}
This keeps type safety and avoids guessing.
2. Using @ts-ignore
@ts-ignore tells the compiler to skip checking the next line.
// @ts-ignore
user.doSomething();
Why this is dangerous
- Real type errors are silenced
- Future developers do not know why the error was ignored
- Refactoring becomes risky
Instead of catching mistakes early, the code fails silently until runtime.
When it might be justified
- Temporary workaround for broken third-party typings
- Only with a clear comment explaining why
// @ts-ignore temporary workaround, remove after library update
Better alternatives
- Fix or extend the type definitions
- Add a proper type guard
- Narrow the type instead of ignoring it
3. Unsafe type assertions (as)
Type assertions force TypeScript to trust you.
const value = getValue() as number;
value.toFixed(2);
If getValue() returns something else, the code compiles but fails at runtime.
const value = "123" as number; // compiles
value.toFixed(); // runtime error
Why this happens often
- It feels like the fastest fix
- The developer “knows” what the value should be
- Compiler errors feel annoying under time pressure
Simple alternative
const value = getValue();
if (typeof value === 'number') {
value.toFixed(2);
}
More scalable alternatives (important for real projects)
- User-Defined Type Guards
Useful when working with API responses or complex objects. - Generics
Let the type flow through the system instead of being forced at one point.
Example with generics:
function parseResponse<T>(data: unknown): T {
return data as T; // centralized and controlled
}
Even here, the assertion is isolated and documented instead of scattered across the codebase.
4. The most dangerous combination
The worst cases usually combine everything:
// @ts-ignore
const data: any = response.data as User;
This code:
- Looks typed
- Is not actually checked
- Fails silently
- Is very hard to debug later
It creates a false sense of safety, which is more dangerous than having no types at all.
What “healthy” TypeScript code looks like
anyis rare and localizedunknownis paired with checksasis used carefully and intentionally- Errors are fixed, not suppressed
- The compiler is treated as a tool, not an obstacle
Key takeaway
If a codebase is full of any, @ts-ignore, and aggressive as,
TypeScript is present only nominally and does not do its main job.
A slightly more verbose solution is usually safer, clearer, and more stable in production — even if it takes a bit more effrot upfront.
