Give Every Suppression an Expiry Date: ts-expect-error over ts-ignore
// @ts-ignore hides bugs. // @ts-expect-error expires them. Both silence a TypeScript error on the next line — but only one of them tells you when the silence is no longer needed. That single difference is why the second one belongs in your codebase and the first one belongs in the past.
Why ts-ignore rots
A // @ts-ignore comment suppresses whatever error sits on the following line, and it keeps doing that forever. Fix the underlying bug, upgrade the library, tighten the type — the comment doesn't care. It stays put, silent, hiding nothing. Now every reader has to wonder: is this suppression still load-bearing, or is it dead weight that's also masking a new error that crept onto that line? You can't tell without deleting it and re-running the compiler, so nobody ever does. Suppressions accumulate, and each one is a place where the type checker has been told to look away permanently.
How ts-expect-error fixes it
// @ts-expect-error does the same suppression with one added rule: if the next line has no error, TypeScript reports Unused '@ts-expect-error' directive. The comment is now a claim — "there is an error here" — that the compiler checks on every build.
// @ts-expect-error — upstream types are wrong, see issue #421
const total = sum(values, { strict: true });
Fix the real issue and the line stops erroring. Immediately, the @ts-expect-error itself starts erroring, telling you to delete it. The suppression cleans up after itself. It cannot outlive the problem it was written for, and it can never quietly mask a different error, because a mismatch surfaces the moment the expected error moves or disappears.
When to use which
| Situation | Directive |
|---|---|
| Known, temporary error you'll revisit | // @ts-expect-error + reason |
| Error you expect a fix/upgrade to remove | // @ts-expect-error + reason |
| "Just make it compile" with no follow-up plan | Fix the type instead |
| Suppressing an error on a whole file | Neither — narrow the scope |
The rule of thumb: reach for @ts-expect-error by default. Every one you write comes with a built-in expiry check. Reserve @ts-ignore for the rare case where an error is genuinely intermittent across environments and a hard "must error" assertion would itself break the build — and even then, comment why.
Power move: enforce it with a linter
Don't rely on discipline. The typescript-eslint rule @typescript-eslint/ban-ts-comment does it mechanically. Its recommended defaults ban @ts-ignore outright (ts-ignore: true) and allow @ts-expect-error only with a description ("allow-with-description"), with a minimumDescriptionLength of 3 characters. The strict preset raises that minimum to 10 characters.
You can go further with descriptionFormat, a regex that forces a house style on every justification — for example requiring ^: TS\d+ because .+$ so each suppression names the error code and the reason. Turn it on and your CI rejects a bare @ts-ignore and a reasonless @ts-expect-error before they ever merge.
Also worth knowing: since TypeScript 5.5, @ts-expect-error works on the line above a JSX/TSX expression too, and a description after the directive is free-form text the compiler ignores — so write it for humans and let the linter police its shape.
The takeaway
@ts-ignore is a suppression with no expiry date. @ts-expect-error is a suppression that deletes itself when it's no longer true. Swap them everywhere, require a reason, and let the compiler tell you when it's safe to clean up.
Resources
- TypeScript 3.9 release notes —
@ts-expect-error - typescript-eslint —
ban-ts-commentrule - TypeScript Handbook — release notes index
Shipping AI-written TypeScript? Yeda AI audits and hardens the codebases that coding agents produce.