Lesson 9 of 30
Running eslint locally is a nicety. Running it in CI and failing the build on it is a deliberate policy decision — and the two aren't redundant.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Most editors already run a linter and a type-checker as you type. So it's tempting to think running eslint and tsc --noEmit again in CI is redundant — the developer already saw the red squiggles, why check again? The answer is that a local check only protects the person who ran it, and only at the moment they ran it. CI checks protect the branch itself, for everyone, on every change, including the ones nobody's editor ever saw.
Treating a static check as a gate means something specific: it's not advisory output someone might glance at, it's a pipeline step that fails the whole run — and, combined with branch protection (the next lesson), blocks the merge — if it doesn't pass. That's a deliberate policy choice, not just "we happen to run this in CI too." A team can run eslint in CI and still let PRs merge with lint errors if nothing enforces it; making it a gate means the tool's opinion is binding, not optional.
- name: Type-check
run: pnpm exec tsc --noEmit
- name: Lint
run: pnpm exec eslint .
Two separate steps, two separate failure modes worth telling apart. tsc --noEmit type-checks the whole project without producing output files — pure verification, and it fails the step (and the job) on the first type error anywhere, even in a file the current PR never touched, if that file's types were already broken or the PR broke them indirectly through a shared type. eslint . lints every file matching the project's config, catching things TypeScript's type system fundamentally can't: react-hooks/purity violations, unused variables, unreachable code, patterns this codebase has specifically flagged as bug-prone before.
Both steps run before the production build in this workflow, not after — a deliberate ordering. Failing fast on a type or lint error means the pipeline never spends the several extra minutes doing a full pnpm build for a change that was already known to be broken. Order steps in a pipeline cheapest-and-most-likely-to-fail first.
node_modules, a stale global eslint config, an uncommitted local-only override, or simply forgetting to run the check before pushing all produce the gap CI exists to close. The gate isn't there because developers are careless — it's there because "ran it locally" isn't a claim about the actual commit sitting on the branch.A step that fails in CI is only useful information until something actually enforces it. The next lesson covers how GitHub's required status checks take a job like this one and turn it from "the pipeline shows red" into "the merge button is physically disabled until it's green" — that enforcement piece is what separates a gate from a suggestion.