skip to content

What do the `// @ts-check` and `// @ts-nocheck` comments do when placed at the top of a file, and how do they interact with TypeScript's `checkJs` compiler option?

level: middleimportance: should knowfreq 46%

answer

  1. comment at the top wins
  2. one opts in, one opts out
  3. per file, never per import graph
  4. nocheck is not .js-only
  5. file-level, not line-level

basics

~20 s

Placed at the top of a file, // @ts-check makes TypeScript type-check that one file even when checkJs is off, and // @ts-nocheck makes it skip that file even when checking is on. Both override the project setting per file.

solid answer

~50 s

`checkJs` is a project-wide switch, but whether a given file is checked is decided per file, and these two comments are the override. `// @ts-check` at the top of a JavaScript file turns the checker on for that file alone — diagnostics come from inference plus its JSDoc — without changing anything for its neighbours. `// @ts-nocheck` does the opposite: it suppresses all type errors in that file, and it works in TypeScript files too, not only JavaScript. The comment has to be at the top of the file, before any statements. Practically this gives you two rollout shapes: leave `checkJs` off and opt files in one at a time with `// @ts-check`, or turn `checkJs` on globally and exempt the stragglers with `// @ts-nocheck`. Editors backed by the TypeScript language service honour `// @ts-check` even in a file with no tsconfig at all.

code

javascript · 12 lines
javascript
// @ts-check

/**
 * @param {string} label
 * @param {number} count
 * @returns {string}
 */
export function summarize(label, count) {
  return `${label}: ${count.toFixed(0)}`;
}

export const line = summarize("items", 3);

go deeper

for a junior

Recall the direction of each comment: @ts-check turns checking on for that file, @ts-nocheck turns it off, and both go at the very top of the file.

for a middle

Explain the interaction with the checkJs setting in both directions, that the scope is exactly one file, and that @ts-nocheck is legal in TypeScript files too — not just JavaScript.

for a senior

Argue the rollout shape: default-on with an explicit exemption list beats default-off opt-in, because exemptions are greppable and visibly decay while unopted files are invisible.

for a principal

Own how the exemption list is governed — who may add one, whether it carries an owner and an expiry, and how the count is tracked so checking coverage is a measured property of the codebase rather than folklore.

## The setting is global, the decision is per file `checkJs` in `tsconfig.json` says "report type errors in JavaScript files". But the compiler ultimately asks the question one file at a time, and two magic comments answer it directly, overriding whatever the project said. ## // @ts-check Put it as a comment at the top of a `.js` file, before any statements: ```js // @ts-check /** @param {string} name */ export function greet(name) { return name.toUpperCase(); } greet(42); // error: Argument of type 'number' is not assignable to parameter of type 'string'. ``` That file is now checked. Nothing about its neighbours changes. The diagnostics are the ordinary TypeScript diagnostics, derived from whatever the compiler can infer plus any JSDoc types the file declares — remember a `.js` file has nowhere to write type syntax, so JSDoc is the only channel for explicit types. Two things about it are worth being precise on. First, for a `tsc` build the file still has to be *in the program* — `allowJs` is what admits `.js` files, and a comment cannot admit a file the compiler never looks at. Second, an editor using the TypeScript language service will honour the comment on a loose file with no project at all, which is why `// @ts-check` is such a common first move: you get real feedback in the editor before touching any configuration. ## // @ts-nocheck The mirror image. At the top of a file, it suppresses the type errors the checker would report in that file: ```js // @ts-nocheck // legacy vendor bundle - not worth annotating ``` Two details matter. It works in TypeScript files as well as JavaScript ones — a `.ts` file with `// @ts-nocheck` at the top stops reporting errors too. And its scope is exactly one file: it does not propagate to imports, and it does not stop other files from complaining about *this* file's exported shapes. Types are still inferred and still flow outward to consumers; only the diagnostics inside the file are silenced. ## Placement Both are comments at the top of the file, ahead of code. A directive buried halfway down does nothing, which is a surprisingly common bug report — the file simply is not checked, or is still checked, and nothing explains why. They are `//` line comments (or block comments); they are not JSDoc tags, so they do not belong inside a `/** ... */` attached to a declaration. ## Why both exist: two rollout shapes **Opt in.** Leave `checkJs` off. Add `// @ts-check` to a file when you touch it, fix what it finds, commit. Nothing else in the repo can break. The cost is that a new untouched file is unchecked by default, so coverage only grows where people happen to work. **Opt out.** Turn `checkJs` on for the whole project and add `// @ts-nocheck` to the files that produce unmanageable noise. Now the default is safe: a new `.js` file is checked from the moment it exists, and the exemptions are visible in the diff and greppable. The cost is a bigger initial push. The opt-out shape is the stronger end state for exactly the reason that makes `@ts-expect-error` better than `@ts-ignore`: an explicit, greppable suppression list decays visibly, while an implicit "not opted in yet" default is invisible. ## What they are not They are file-level switches, not error suppressors for a line — that is `// @ts-expect-error` and `// @ts-ignore`, which target the next line only. And neither has any effect on emit: a `// @ts-nocheck` file is still compiled and still output. They move diagnostics, nothing else.

  • Does `// @ts-nocheck` in a file stop errors that other files report about it?
    No. Its scope is the file it sits in. The compiler still infers the file's exported types and still hands them to importers, so a consumer that misuses an export gets an error in the *consumer's* file. The directive only silences diagnostics reported inside its own file.
  • If a JavaScript file has `// @ts-check` but the project does not set `allowJs`, does `tsc` check it?
    No — for a `tsc` build the file is not in the program at all, so there is nothing to check and the comment is inert. `allowJs` is what admits `.js` files. Editors are the exception: the language service honours `// @ts-check` on a standalone file even with no project configuration.
  • Why would a team prefer `checkJs` plus `// @ts-nocheck` exemptions over per-file `// @ts-check` opt-in?
    Because it makes the safe state the default. Every new or renamed file is checked automatically, and the exemptions are an explicit, greppable list that shows up in review and shrinks visibly. With opt-in, an unchecked file is indistinguishable from one nobody has got to yet, so coverage silently stalls.

saying these in an interview costs you the question

  • Thinks // @ts-check works anywhere in the file
  • Believes // @ts-nocheck only works in .js files
  • Says // @ts-nocheck removes the file from the build output
  • Assumes the directives suppress errors in imported files
  • Confuses file-level @ts-check with line-level @ts-ignore

context