In TypeScript, what is the difference between the `// @ts-expect-error` and `// @ts-ignore` comments, and why is one usually preferred?
answer
- both silence the next line
- one of them expects to be needed
- unused directive is itself an error
- suppressions that cannot rot
- no error-code targeting in either
basics
~20 sBoth suppress type errors on the line that follows them. // @ts-expect-error additionally requires an error to be there — if the line becomes valid, the compiler reports the directive as unused, so stale suppressions surface instead of rotting.
solid answer
~50 sThey suppress identically: any checker error reported on the next line is silenced. The difference is what happens when there is no error. `// @ts-ignore` says nothing — it silently does nothing forever, so a suppression left over from a bug fixed two years ago is invisible. `// @ts-expect-error` asserts that the next line *does* fail, and when it stops failing the compiler reports "Unused '@ts-expect-error' directive", which fails the build and points you at the line to delete. That self-cleaning property is why it is the default choice, and why lint configs commonly ban `@ts-ignore` outright. Two caveats apply to both: they suppress every error on that one line, with no way to target a specific error code, and the "next line" is literal — an error the checker reports on a different line of a multi-line expression is not covered. In JSX children they must be written as a JSX comment expression.
code
typescript · 9 linesdeclare function greet(name: string): string;
// @ts-expect-error - call site not migrated yet
greet(42);
// @ts-ignore
greet(42);
export {};go deeper
Know that both comments silence the type error on the line below them, and that @ts-expect-error is the one to write because the compiler complains once the error is gone.
Explain the unused-directive error precisely, that suppression covers every error on exactly the following line, and that neither can target a specific error code.
Show how you keep suppressions honest in a real codebase: a mandatory prose reason, the narrowest possible expression under the directive, and @ts-expect-error used as an executable assertion in type tests.
Own the policy: whether suppressions are banned, budgeted or reviewed, what a suppression must record to be merged, and how the suppression count is tracked as a health signal rather than left to individual discretion.
## The shared behaviour Both directives are line comments placed immediately above a line of code, and both tell the checker to drop errors reported on that following line: ```ts declare function greet(name: string): string; // @ts-ignore greet(42); // @ts-expect-error - legacy call site, fix in the parser rewrite greet(42); ``` Both compile. Neither changes emit, neither changes inferred types, and neither is visible at runtime — they are instructions to the checker and nothing else. ## The one difference: what happens when the error goes away Suppose `greet` is later widened to accept `string | number`. Now both calls are perfectly legal. - The `// @ts-ignore` line keeps compiling. The comment is still there, still suppressing nothing, and nothing will ever tell you. Next time `greet` genuinely breaks at that call site, the comment silently swallows the error. - The `// @ts-expect-error` line now fails the build: ``` error TS2578: Unused '@ts-expect-error' directive. ``` That is the whole design. `@ts-ignore` is a permanent, invisible hole. `@ts-expect-error` is an assertion with an expiry attached: it holds only while the problem it documents still exists, and the moment the problem is fixed the compiler tells you to delete the comment. A codebase full of `@ts-ignore` accumulates dead suppressions that quietly cover real regressions; a codebase using `@ts-expect-error` cannot, because dead ones break the build. The same assertion property makes `@ts-expect-error` a legitimate *testing* tool: in type-level tests you write the deliberately-illegal call under the directive, and if a refactor accidentally makes it legal — widening an API you meant to keep narrow — the test fails. ## Scope and precision Both are blunt in the same two ways, and interviewers like probing this. **All errors, not one.** A directive suppresses *every* error reported on that line. If a call has both a wrong argument type and an unresolved name, one directive hides both, including the one you never inspected. There is no syntax for restricting the suppression to a particular error code; the text you write after the directive is free-form prose, not a filter. **Literally the next line.** The compiler attributes each error to a position, and the directive covers the line after the comment. In a multi-line call or object literal, the error may be reported on the line of the offending *argument*, not the line the expression starts on, so a directive above the opening line does not always land: ```ts declare function configure(opts: { retries: number }): void; // @ts-expect-error - retries must be a number configure({ retries: "3", }); ``` Whether this is covered depends on where the checker reports the error; when a directive appears to "not work", moving it directly above the reported line is the usual fix, and with `@ts-expect-error` you find out immediately because the directive reports itself unused. ## Where they work Both work in `.ts`/`.tsx` files and in JavaScript files that are being checked — under `checkJs` or a file-level `// @ts-check`. In an unchecked `.js` file they are inert, because nothing is reporting errors to suppress. They are checker directives, not a parser escape hatch: they do not make invalid syntax compile. Inside JSX children, a `//` comment is just text, so the directive has to be written as a JSX comment expression: ```tsx <Panel> {/* @ts-expect-error - prop type is wrong upstream */} <Widget size="large" /> </Panel> ``` ## The habit to describe in an interview Reach for `@ts-expect-error`, always with a short prose reason after it — why the error is expected and what would let you remove it. Treat `@ts-ignore` as something you justify rather than something you use, since its only real advantage is that it stays quiet, which is precisely the property you do not want. Where a codebase must allow suppressions, tooling can require the comment to carry a description, so a bare directive with no explanation never reaches the main branch.
- How can `// @ts-expect-error` be used deliberately as a test?In type-level tests you write a call that *must* be rejected — passing the wrong argument type, or reading a property you intend to keep private — under the directive. While the API stays correctly narrow the file compiles. If a refactor accidentally widens the type, the error disappears, the directive reports itself unused, and the build fails. It turns "this must not type-check" into an enforced assertion.
- A line has two separate type errors and you add one `// @ts-expect-error`. What is suppressed?Both. The directive silences every error reported on that line, with no way to scope it to one diagnostic or error code — the text you write after the directive is a comment, not a filter. That is a real risk: you suppress a known problem and unknowingly hide a second one beside it. Prefer narrowing the expression so the directive covers as little code as possible.
- Do these directives work inside a .js file?Yes, provided the file is actually being checked — under `checkJs` or with a file-level `// @ts-check`. In an unchecked JavaScript file nothing is reporting errors, so the directives are inert. Inside JSX children, both must be written as a JSX comment expression, since a `//` comment there is just text rendered as children.
saying these in an interview costs you the question
- Thinks @ts-ignore reports something when the error disappears
- Believes the text after the directive filters by error code
- Assumes a directive suppresses errors for the whole block
- Says the directives change emitted JavaScript
- Treats @ts-expect-error as suppressing more than @ts-ignore does