completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Linting with ESLint

Use ESLint as an automatic code proofreader: understand rules, configs, editor squiggles, fix-on-save, and which warnings to treat as errors.

By the end, you can
  • 01
    Read a lint messageFind the rule id, the line, the severity, and the fix hint in an ESLint message.
  • 02
    Set rules in a configRead a small ESLint config file, choose a severity for each rule, and name a few useful rules.
  • 03
    Use ESLint in your editorKnow how squiggles, quick fixes, and fix on save work in VS Code.

What a linter does

A linter is a program that checks your code for likely mistakes. It does this before you run the code. ESLint is the most widely used linter for JavaScript. It can find things like these:

  • a misspelled variable name;
  • a comparison your team chose not to use, like ==;
  • code after a return, which can never run;
  • a value you made and then never used.

ESLint only reads your code. It doesn't run your functions or click around your page. It reads the file and checks it against a list of rules. A rule is one named check, such as "no unknown names". When a rule finds a problem, ESLint gives you a message. Each message tells you three things:

  • the rule id: the rule's name, like no-undef;
  • the line and column: where the problem is in the file;
  • the severity: how serious the problem is, either a warning or an error.
Real-life analogyA linter is a spell checker for code

A spell checker marks a word that looks wrong before you send your message. A linter does the same for code. It marks names, operators, and team rules that look wrong, before you run anything.

In real life: A red line under a typo
In JavaScript: A lint message about totla
In real life: A grammar rule: end with a period
In JavaScript: An ESLint rule: use ===
In real life: Autocorrect fixes a simple typo
In JavaScript: --fix makes safe changes, like let to const
In real life: Some sentences need a person to fix them
In JavaScript: Some bugs need tests and debugging

You met strict mode, semicolons, and coding style in earlier lessons. A linter checks those habits for you while you type. You'll see two commands in this lesson. The command npm init @eslint/config@latest sets ESLint up, and the command npx eslint runs it. A later lesson explains npm and package.json. For now, you only need to know what these two commands do.

A name is created, then usedPop out in the code editor (opens in a new tab)JavaScript
const userName = "Asha";console.log(userName);

Line 1 creates a variable called userName and stores "Asha" in it. Line 2 prints that variable, so you see Asha. Now imagine that line 2 says userNmae instead. No variable has that name. The no-undef rule ("no undefined names") reports it before you run the code.

The picture and the step-through below check a different two-line file. Its line 2 has a typo: totla instead of total.

Left: an editor window with two lines of code, const total = 1 + 2 and console.log(totla). An arrow labeled reads points to an ESLint window with one message: no-undef, line 2, column 13, error.editorconst total = 1 + 2;console.log(totla);the code is only readreadsESLintno-undefline 2, column 13errorone message
ESLint reads the file but doesn't run it. Its message says which rule found the problem (no-undef), where the problem is (line 2, column 13), and how serious it is (an error).
Watch a linter inspect a file
Step 0 of 2Ready
Your turn: follow the blue line

Pick the correct or the misspelled version. These results were saved from a real ESLint run, and this lesson's tests check them.

Running in
  1. script
Next: line 1
console.log(totla);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Pick how line 2 is spelled

Changing the spelling starts the saved lint check again.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Linting helps even when the code runs fine. The next example prints Welcome with no error. But the no-unused-vars rule ("no unused variables") points at line 1. The name made there is never used. Often that means you forgot to show it, or forgot to delete it.

A value that is made but never usedPop out in the code editor (opens in a new tab)JavaScript
const userName = "Asha";console.log("Welcome");

Line 1 creates userName. Line 2 prints only the word Welcome. So the output is Welcome, and userName is never used. To fix the warning, use the name.

Use the value you madePop out in the code editor (opens in a new tab)JavaScript
const userName = "Asha";console.log("Welcome", userName);

Line 1 is the same as before. Line 2 now prints userName too, so the output is Welcome Asha. The name is used, so no-unused-vars has nothing to report.

Lint this code: rules report messages

INTERACTIVE

The list that ESLint prints is like a checklist. Each item is one message, and each message names the rule that found it. In the playground below:

  • Pick a snippet with the buttons at the top.
  • Turn rules on and off with the rule buttons.
  • Read the messages. You see the messages from every rule that is on, all in one list.

For example, the "Loose equality" snippet uses ==. When the eqeqeq rule is on, ESLint reports it. The name eqeqeq means "equals, equals, equals": always use ===.

Lint this code
Loose equalityPop out in the code editor (opens in a new tab)JavaScript
if (score == "10") {  console.log("match");}
Saved lint messages2 problems
  1. eqeqeq line 1, column 11 error

    Expected '===' and instead saw '=='.

    No safe automatic fix
  2. no-undef line 1, column 5 error

    'score' is not defined.

    No safe automatic fix
Try it yourself
Snippet:

2 saved ESLint messages. Each message below shows the rule id, the severity, the line, the column, and whether ESLint can fix it safely.

These results were saved from a real ESLint run, and this lesson's tests check them. ESLint itself doesn't run in your browser.

To read a message, ask four questions:

  1. Which rule found it? The rule id tells you.
  2. Where is it? The line and column tell you.
  3. How serious is it? The severity tells you: warning or error.
  4. Can ESLint fix it for you? Some rules can make a safe change by themselves.

You can search for any rule id online. The id no-undef means "a name was used but never created". The id prefer-const means "this let never changes, so use const".

Saved results, not live ESLint

This page doesn't run ESLint in your browser. ESLint is too big to load into a lesson page. Instead, the lesson stores results from a real ESLint run. The lesson's tests run ESLint 9's Linter and check that every stored line, column, rule id, message, severity, and fix flag still matches.

ESLint rules and configs

RULEBOOK

A rule is one check, like "always use ===". A config is a file that lists which rules to use. ESLint reads the config before it checks your files. ESLint 9 looks for a config file called eslint.config.js. This kind of config is called a flat config, because it exports one plain list of settings. Most configs start with ESLint's recommended rules, then add a few choices of their own.

Real-life analogyRules are house rules; a config is the list on the fridge

A family writes its house rules on one list and sticks it on the fridge. Then nobody has to argue about them again. An ESLint config is that list for your project's code.

In real life: Take your shoes off at the door
In JavaScript: eqeqeq: use === instead of ==
In real life: Be quiet after 10 pm
In JavaScript: no-console: warn about console.log in app code
In real life: The list of house rules on the fridge
In JavaScript: eslint.config.js
A small config file: eslint.config.jsJavaScript
import js from "@eslint/js"; export default [  js.configs.recommended,  {    languageOptions: {      globals: {        document: "readonly",        console: "readonly"      }    },    rules: {      eqeqeq: "error",      "no-unused-vars": "warn"    }  }];

Line by line:

  1. Line 1 loads ESLint's recommended rules from the @eslint/js package.
  2. Line 4 adds those recommended rules to the config list.
  3. Lines 8 and 9 tell ESLint that document and console already exist, because the browser provides them.
  4. Lines 13 and 14 turn on two rules and say how serious each one is.

ESLint reads this file in Node.js when it starts. The file never runs in your web page.

Each rule gets a severity. You can write it as a word or as a number:

  • "off" or 0: the rule is turned off.
  • "warn" or 1: ESLint shows a warning.
  • "error" or 2: ESLint shows an error.

A warning is like a sticky note: worth reading, but your work can go on. An error is like a stop sign. When npx eslint finds an error, the check fails. Teams often run the same check automatically before new code joins the shared project. That automatic check is called CI, short for "continuous integration". An error makes CI fail too. Use errors for real bugs and for rules your team never wants broken.

Three cards for the three severities. off, or 0: the rule is silent and the check passes. warn, or 1: ESLint shows a warning and the check passes. error, or 2: ESLint shows an error and the check fails."off"or 0rule is silentpasses"warn"or 1shows a warningpasses"error"or 2shows an errorfails
Each rule in the config gets one severity. Only "error" makes npx eslint fail. A warning is shown, but the check still passes.

Warnings don't make the check fail. If you want warnings to fail it too, run npx eslint . --max-warnings 0. This is a choice each project makes. It doesn't change any rule.

The table below lists the config words you'll see most. Read each row as: the word, what it means, and an example.

Words you'll see in an ESLint config
WordWhat it meansExample
RuleOne named check.no-undef reports names that were never created.
SeverityHow serious a rule's message is.warn is a gentle note. error makes the check fail.
Recommended configA ready-made set of rules.js.configs.recommended comes from the @eslint/js package.
GlobalsNames that already exist without your file creating them.Browser code often lists document and console as readonly. Many teams use the globals package for this list.
Disable commentA comment that turns a rule off in one place.// eslint-disable-next-line no-alert, followed by a short reason.

A global is a name your file can use without creating it, because the place where the code runs provides it. In a browser, document is the web page. Node.js has no web page, so it has no document. Tell ESLint where your code runs. Then it won't report real names like document as typos. Mark these names readonly, unless your code is meant to replace them.

Turn a rule off only where you must

Sometimes one line has to break a rule on purpose. Then turn the rule off for that one line with a disable comment, and write the reason next to it. Don't turn the rule off for the whole project. The reason is a note for the next person who reads the code.

Turn off one rule for one line, with a reasonPop out in the code editor (opens in a new tab)JavaScript
const score = 10;// eslint-disable-next-line eqeqeq -- legacy form valueif (score == "10") console.log("Ready");

Line by line:

  1. Line 1 stores the number 10.
  2. Line 2 is the disable comment. It turns off eqeqeq for the next line only. The text after -- is the reason: the value comes from an old form.
  3. Line 3 uses == on purpose and prints Ready.

Keep an exception this small. Suppose line 3 changes later and no longer breaks the rule. Then ESLint can tell you that the comment isn't needed any more, if that check is turned on in the config.

Fixing on save

STEP THROUGH

ESLint can fix some problems by itself. A rule that can do this is called fixable. ESLint only makes a fix when it is sure the fix is safe. Take prefer-const. If a variable is made with let and never gets a new value, ESLint can change let to const. The program still prints the same thing.

Other rules need you to decide. If you typed totla, the no-undef rule can't know what you meant. You might have meant total, or you might want a new variable. So ESLint reports the problem and leaves the fix to you.

Left: prefer-const. The card let limit = 3 becomes const limit = 3 with --fix. Labeled safe to fix. Right: no-undef. The card console.log(totla) leads to a question mark, because ESLint can't know which name you meant. Labeled needs you.prefer-constlet limit = 3;--fixconst limit = 3;safe to fixno-undefconsole.log(totla);which name??needs you
On the left, ESLint is sure the change is safe, so --fix makes it. On the right, ESLint can't tell which name you meant, so it only reports the problem and you fix it.
Watch one safe ESLint fix
Step 0 of 2Ready
Your turn: follow the blue line

Step through one real fix. The lesson saved the fixed code from ESLint, and the tests check it against ESLint.

Running in
  1. script
Next: line 1
console.log(limit);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Don't change every let to const. Use let when the variable really changes. In the next example, line 2 gives score a new value. ESLint sees that, so prefer-const reports nothing. The program prints 2.

A score that changes needs letPop out in the code editor (opens in a new tab)JavaScript
let score = 1;score = 2;console.log(score);

Line 1 sets the score to 1. Line 2 changes it to 2. Line 3 prints 2. The change on line 2 is on purpose, so let is the right choice. A lint rule is feedback about this code. It isn't an order to use one keyword everywhere.

You can also run ESLint in a terminal: the window where you type commands. The command npx eslint . checks the whole project and lists the problems. The dot means "this folder". The command npx eslint . --fix does the same, and also makes the safe fixes. The npm lesson explains npx later. For now, read it as "run ESLint on this project".

Commands to set up, check, and fixShell
npm init @eslint/config@latestnpx eslint .npx eslint . --fixnpx eslint . --max-warnings 0

Line by line:

  1. Line 1 starts ESLint's setup helper, which creates a config file for you.
  2. Line 2 checks every file in the folder and lists the problems.
  3. Line 3 does the same, and also makes the safe fixes.
  4. Line 4 makes the check fail if even one warning is left.

ESLint inside your editor

INTERACTIVE

ESLint helps most when you see its messages while you type. In VS Code, install the ESLint extension. An extension is an add-on for the editor. The ESLint extension does four things:

  • It reads your project's config.
  • It draws a wavy line, called a squiggle, under each problem.
  • It offers quick fixes that you can click.
  • It can make the safe fixes each time you save the file.

To fix on save, add the setting below to your VS Code settings. Recent versions of VS Code use the value "explicit". It means "fix when I save the file myself", not when the editor saves by itself. Older examples use true instead.

Fix on save in VS Code
.vscode/settings.jsonJSON
{  "editor.codeActionsOnSave": {    "source.fixAll.eslint": "explicit"  }}
What save can dosafe fixes
let limit = 3;console.log(limit);

Saving can change let limit to const limit, because ESLint checked that limit never gets a new value.

Try it yourself

With this setting, the VS Code ESLint extension makes ESLint's safe fixes when you save the file. Recent VS Code uses explicit. Older examples sometimes show true.

This setting needs the VS Code ESLint extension. ESLint also needs a config file in your project.
The VS Code setting that fixes on saveJSON
{  "editor.codeActionsOnSave": {    "source.fixAll.eslint": "explicit"  }}

Line 3 is the setting that matters. The name source.fixAll.eslint means "make all of ESLint's safe fixes".

The extension doesn't replace learning the rules. When you see a squiggle, read the message and look at the line. Then choose one of these:

  • accept the quick fix;
  • change the code yourself;
  • or, if the line must break the rule, add a disable comment with a reason.

With that habit, lint messages teach you something each time.

Linter or formatter?

SORT

The last lesson showed Prettier, which is a formatter. A formatter fixes how code looks: spaces, line breaks, and indentation. Then nobody has to argue about layout. A linter looks for code that is probably wrong, such as:

  • names that are never used;
  • names that were never created;
  • code that can never run;
  • code that breaks a rule your team chose.

The table compares the two tools. Read each row as: the tool, what it checks, and what it does.

What a formatter does and what a linter does
ToolWhat it checksWhat it does
FormatterHow the code is laid out.It rewrites spaces, line breaks, quotes, and the comma after the last item in a list.
LinterWhether the code breaks a rule.It reports a message. Sometimes it can also make a safe fix.
Both togetherWhether the whole team writes code the same way.Prettier handles layout, and ESLint handles likely mistakes.
Linter or formatter?
  • A variable is created but never used
  • The indentation is uneven
  • The code uses ==, but the team wants ===
  • A line is too long to read easily
  • A variable is used before the line that creates it
  • A list that spans several lines has no comma after its last item
  • An extra semicolon after a statement
Try it yourself
0 of 7 correct

Put each problem under the tool that usually helps first. Some teams set the tools up differently, but these are good choices to start with.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you'll use linting

In a real project, ESLint runs in three places:

  1. In your editor, which shows squiggles while you type.
  2. In a terminal, where you check the whole project before you share your work.
  3. In CI, the automatic check that runs before code joins the shared project.
Three windows in a row, joined by arrows. An editor window shows log(totla) while you type. A terminal window runs npx eslint . before you share. A CI check window runs npx eslint . before merging.editorlog(totla);while you typeterminalnpx eslint .before you shareCI checknpx eslint .before merging
The same rules run in three places: in your editor while you type, in a terminal before you share your work, and in CI before your code joins the shared project.

Imagine you are building a small shopping-cart page. A linter can't tell you whether the checkout page looks good. But it can catch mistakes like these:

  • cartTotal typed as cartTtoal;
  • code after a return, which never runs;
  • two keys in one object with the same name, where the second one quietly replaces the first.
A safe fix keeps the output the samePop out in the code editor (opens in a new tab)JavaScript
const limit = 3;console.log(limit);

This is the code after the prefer-const fix. Line 1 uses const, because limit never changes. Line 2 prints 3. The fix changed line 1, but the output is still 3.

Check whether the cart is emptyPop out in the code editor (opens in a new tab)JavaScript
const cart = { total: 12 };if (cart.total == 0) {  console.log("Your cart is empty");} else {  console.log("Ready to pay");}

Line 1 makes a cart with a total of 12. Line 2 checks whether the total is 0. It isn't, so the else branch runs and prints Ready to pay. The code works, but the eqeqeq rule still reports the == on line 2. Exercise 6 asks you to fix it.

The same check with ===Pop out in the code editor (opens in a new tab)JavaScript
const cart = { total: 12 };if (cart.total === 0) {  console.log("Your cart is empty");} else {  console.log("Ready to pay");}

Only the operator changed, from == to ===. The total is still 12, so the code still prints Ready to pay. Before it compares, == can quietly turn a value into another type. For example, "0" == 0 is true, because the text "0" becomes the number 0. The === operator never does that. That's why many teams turn on eqeqeq.

Code after return never runsPop out in the code editor (opens in a new tab)JavaScript
function getDrink() {  return "tea";  console.log("not reached");} console.log(getDrink());

Line 2 ends getDrink right away and gives back "tea". Line 3 comes after the return, so it can never run. The no-unreachable rule reports it. Line 6 prints tea. If you need line 3, move it above the return. If you don't, delete it.

A repeated key replaces the first onePop out in the code editor (opens in a new tab)JavaScript
const settings = {  theme: "dark",  theme: "light",};console.log(settings.theme);

The object has two properties named theme. JavaScript keeps the last one, so line 5 prints light. This is allowed JavaScript, so nothing crashes. But it is easy to miss when you read the code. The no-dupe-keys rule ("no duplicate keys") catches it.

A tiny model of a CI checkPop out in the code editor (opens in a new tab)JavaScript
const lintMessages = ["no-undef"];const shouldMerge = lintMessages.length === 0;console.log(shouldMerge ? "Ready to merge" : "Fix lint messages first");console.log("First rule:", lintMessages[0]);

This code pretends to be a CI check. Line by line:

  1. Line 1 is a list of lint messages. It holds one rule id: no-undef.
  2. Line 2 checks whether the list is empty. It isn't, so shouldMerge is false.
  3. Line 3 prints Fix lint messages first, because shouldMerge is false.
  4. Line 4 prints the first rule: First rule: no-undef.

A real CI check runs ESLint itself. But the idea is the same: an error stops the code from joining the shared project.

A simple routine for beginners
  1. Write a small piece of code.
  2. Read any squiggles before you run the page.
  3. Use the safe quick fixes, but make sure you understand each one.
  4. Run npx eslint . before you call the work done.
  5. If a rule seems wrong, ask: should the config change, or should my code change?

Common misconceptions

"If lint passes, the app works."

Passing lint only means the rules that are on found no problem. You still need to run the code, test what it does, and think it through.

"Every lint message is a bug."

Some messages are about style, or about code that will be hard to change later. Check the severity and the rule id to see how urgent a message is.

"An autofix means ESLint understands my goal."

An autofix only means the rule's author marked that change as safe. ESLint doesn't know what your app is supposed to do.

"Formatters and linters do the same job."

They overlap a little. But Prettier mostly fixes layout, and ESLint mostly finds likely mistakes.

"Disable comments are harmless."

Sometimes a disable comment is the right choice. But each one hides a message. Keep each one to a single line, and write why it is there.

Code with no lint problems can still be wrongPop out in the code editor (opens in a new tab)JavaScript
const cart = { total: 12 };const discount = 20;console.log(cart.total - discount);

ESLint finds no problem in these three lines. But line 3 prints -8: a discount of 20 taken off a cart total of 12. Can a discount be bigger than the total? ESLint can't know that. Your team decides that rule, and tests check it.

Practice: make ESLint useful

6 EXERCISES
Exercise 1 · Warm-upPredict the rule id

Which rule reports the misspelled name in this code?

Starter codePop out in the code editor (opens in a new tab)JavaScript
let count = 1;
console.log(cout);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFix the code so prefer-const is happy

    Change the code so the prefer-const rule has nothing to report. Then type what the fixed program prints.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    let total = 1 + 2;
    console.log(total);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeWrite the rule setting

      In an ESLint config, make the eqeqeq rule an error, so that any == fails the check.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      import js from "@eslint/js";
      
      export default [
        js.configs.recommended,
        {
          rules: {
            eqeqeq: "error"
          }
        }
      ];

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeSet up fix on save

        Look at the VS Code setting from this lesson. What value goes after source.fixAll.eslint?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · PracticeChoose warn or error

          Your team agrees that unknown variable names must never be merged. Should the no-undef rule be a warning or an error?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeCheck a cart before checkout

            A shopping page should say the cart is empty only when the total is 0. The team has turned on the eqeqeq rule. Change the comparison on line 2 so the rule has nothing to report. Then check what the code prints.

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            const cart = { total: 12 };
            if (cart.total == 0) {
              console.log("Your cart is empty");
            } else {
              console.log("Ready to pay");
            }

            Answer, then press Check. Spacing and letter case don’t matter.

              Quiz: check your understanding

              7 QUESTIONS

              Try each question before you open the explanations. Some wrong choices look right on purpose, so read each one carefully.

              Lesson quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What is a linter's main job?

                Choose an answer to see the explanation.

              2. Question 2 of 7Which rule reports this code?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log(totla);

                Choose an answer to see the explanation.

              3. Question 3 of 7Which severity usually makes a script or a CI check fail?

                Choose an answer to see the explanation.

              4. Question 4 of 7What does this program print after ESLint makes the prefer-const fix?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                let limit = 3;
                console.log(limit);

                Choose an answer to see the explanation.

              5. Question 5 of 7Which file name is the ESLint 9 config file used in this lesson?

                Choose an answer to see the explanation.

              6. Question 6 of 7Which tool is most likely to fix uneven indentation by itself?

                Choose an answer to see the explanation.

              7. Question 7 of 7Why should disable comments be rare?

                Choose an answer to see the explanation.

              Key takeaways

              • A linter reads your code before it runs and reports anything that breaks a rule.
              • Each ESLint message gives a rule id, a severity, a line, a column, and a short message. Some also offer a safe fix.
              • A config lists the rules to use. In ESLint 9, the config file is eslint.config.js.
              • Use error for problems that must stop the work, and warn for advice.
              • Squiggles and fix on save in your editor let you see and fix problems while you type.
              • A formatter fixes layout. A linter finds likely mistakes.

              Remember the one-liner.
              A linter is an automatic checker for code: it finds likely mistakes and broken team rules before you run the code.

              Coming next: Thinking like a developer.

              CompleteFrontend Clear concepts. Working examples.