- Published on
What is a Linter? Understanding ESLint and Why It Matters for Clean Code
- Authors
What is a Linter? Understanding ESLint and Why It Matters for Clean Code
When multiple developers work on the same codebase, style inconsistencies and hidden errors can quickly pile up.
That’s where linters come in, they automatically check your code for potential problems and enforce a consistent style across your project.
In this post, we’ll explore what a linter is, why you should use one, and why ESLint has become the go-to tool in the JavaScript and TypeScript ecosystem.
What Linters Detect
A linter analyzes your code and detects various categories of issues before they cause bugs or inconsistencies.
- Syntax errors → Problems that make code invalid or non-runnable. Examples: - Missing parentheses or brackets → if (user.isLoggedIn {
- Unclosed strings → console.log("Hello)
- Misplaced or missing returnstatements.
 
- Missing parentheses or brackets → 
- Code quality issues → Patterns that don’t break syntax but can lead to runtime errors or confusion. Examples: - Unused variables → Declaring const result = computeValue();but never usingresult.
- Unreachable code → Having statements after a return.
- Shadowed variables → Reusing a variable name from an outer scope.
 
- Unused variables → Declaring 
- Style inconsistencies → Formatting and style differences that make code harder to read. Examples: - Missing semicolons or inconsistent indentation.
- Using "double quotes"in one file and'single quotes'in another.
- Mixing tabs and spaces for indentation.
 
- Best practice violations → Code that technically works but goes against recommended patterns. Examples: - Using deprecated APIs (e.g., ReactDOM.render()in React 18).
- Using varinstead ofletorconst.
- Calling asynchronous functions without await.
- Using ==instead of===, which can cause subtle type coercion bugs.
 
- Using deprecated APIs (e.g., 
Linters like ESLint go beyond catching mistakes, they enforce conventions and prevent bad habits from ever landing in your repository.
Why Use a Linter?
1. Catch Errors Early
Linters detect issues before your app even runs.
For example, ESLint can warn you about an undefined variable or a missing await in an async function, saving you debugging time later.
2. Ensure Consistency
A linter enforces a unified coding style across your team, avoiding unnecessary debates about tabs vs. spaces or where to put commas.
3. Improve Code Quality
Linters encourage cleaner, more maintainable code by warning against complex or risky patterns (like deeply nested callbacks or unused imports).
4. Integrate with Your Workflow
They run automatically in your IDE, during commits (via pre-commit hooks like Husky), or in CI/CD pipelines, keeping your codebase clean at every step.
Focus on ESLint: The Standard for JavaScript and TypeScript
ESLint is the most popular linter in the JavaScript ecosystem, and it’s widely used for React, Node.js, and TypeScript projects.
🔧 Key Features
- Highly configurable → Customize every rule to fit your style.
- Plugin ecosystem → Extend with community plugins (e.g., React, Jest, security, accessibility).
- TypeScript support → Full integration via @typescript-eslint.
- Autofixable rules → Many issues can be automatically fixed with:npx eslint . --fix
- Integrations → Works with VS Code, WebStorm, GitHub Actions, and almost every CI system.
Configuring ESLint
You can set up ESLint in your project with:
npx eslint --init
If you’re using ESLint v8.23 or later, this command will create a Flat Config file named eslint.config.mjs (instead of the older .eslintrc.json format). This modern format uses JavaScript modules and provides better performance and clarity.
Here’s a simple example configuration you might use in a Next.js + TypeScript project:
// eslint.config.mjs
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import prettier from 'eslint-config-prettier'
import { FlatCompat } from '@eslint/eslintrc'
const compat = new FlatCompat({ baseDirectory: import.meta.dirname })
export default [
  js.configs.recommended, // Core JavaScript best practices
  ...tseslint.configs.recommended, // TypeScript-specific linting
  ...compat.extends('next/core-web-vitals'), // Next.js and React recommendations
  prettier, // Avoid rule conflicts with Prettier
  {
    files: ['**/*.{js,jsx,ts,tsx}'],
    rules: {
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      '@typescript-eslint/no-unused-vars': [
        'warn',
        { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
      ],
      semi: ['error', 'always'],
    },
  },
]
Explanation
Let’s break down what’s happening:
js.configs.recommended
Loads ESLint’s recommended core rules, covering basic JavaScript issues such as:
- Unused variables
- Unreachable code
- Missing return statements
- Syntax errors
tseslint.configs.recommended
Adds TypeScript-aware rules, for example, catching:
- Unused imports
- Incorrect type usage
- Variables declared but never used in .ts/.tsxfiles
compat.extends('next/core-web-vitals')
Applies Next.js and React rules, including:
- Accessibility best practices (a11y)
- React Hooks usage checks
- Warnings for performance issues (like using <img>instead of<Image>)
prettier
Turns off formatting-related lint rules that would otherwise conflict with Prettier.
rules
Custom project-specific adjustments:
- 'no-console': ['warn']→ allows- console.warnand- console.error, but warns about other console calls.
- '@typescript-eslint/no-unused-vars'→ warns about variables or parameters that are declared but not used (ignores ones starting with- _).
- 'semi': ['error', 'always']→ enforces the use of semicolons.
This setup gives you:
- Reliable JavaScript + TypeScript linting
- Next.js best practices built in
- Automatic compatibility with Prettier
- Simple, clear rules that make your codebase consistent, readable, and bug-free
Once this file is in place, you can lint your project with:
npm run lint
or automatically fix fixable issues:
npm run lint -- --fix
ESLint vs Other Linters
| Feature | ESLint | TSlint (deprecated) | JSHint | StandardJS | 
|---|---|---|---|---|
| Language Support | JavaScript, TypeScript | TypeScript | JavaScript | JavaScript | 
| Configurable | ✅ Yes | ⚠️ Limited | ⚠️ Limited | ❌ No (fixed style) | 
| Plugin Ecosystem | 🟢 Large | 🔴 Small | 🔴 Small | 🔴 Minimal | 
| Autofix Support | ✅ Yes | ⚠️ Partial | ⚠️ Partial | ✅ Yes | 
| Actively Maintained | 🟢 Yes | 🔴 No | 🟡 Rarely | 🟢 Yes | 
Since TSlint was officially deprecated in favor of ESLint, nearly all modern JavaScript and TypeScript projects use ESLint today.
Best Practices for Using ESLint
- Combine ESLint with Prettier for automatic code formatting.
- Add ESLint checks to your Git pre-commit hooks with Husky or lint-staged.
- Run it in your CI pipeline to enforce code quality in pull requests.
- You could use recommended packages like eslint-config-airbnb or eslint-plugin-react
Conclusion
Linters are essential for keeping codebases clean, consistent, and error-free, especially in collaborative projects. Among them, ESLint stands out as the most flexible and powerful tool for JavaScript and TypeScript developers.
Whether you’re working solo or in a large team, adding ESLint to your workflow ensures your code remains professional, readable, and maintainable for the long run.
Want to see ESLint in action? Check out its official documentation or try adding it to your project with npx eslint --init.