Skip to main content

Coding Style, ESLint, and Prettier to Write Clean JavaScript Code

Code is read far more often than it is written. Every line you write today will be read dozens of times by you, your teammates, and future maintainers. Inconsistent formatting, unclear naming, and sloppy structure slow everyone down. Arguments about tabs versus spaces, semicolons versus no semicolons, and where to put curly braces waste hours of team time that could be spent building features.

The solution is simple: adopt a coding style, automate its enforcement with tools, and never think about formatting again. This guide covers why coding style matters, the most popular JavaScript style guides, the specific formatting rules you should follow, and how to set up ESLint and Prettier so that your code is automatically linted and formatted every time you save a file.

Why Coding Style Matters

Readability

You spend roughly 10 times more time reading code than writing it. Consistent style makes code scannable and predictable:

// Inconsistent style: every line is a puzzle
function processData(data){
let Result = []
for(let i=0;i<data.length;i++){
if(data[i].active==true){
Result.push(data[i].name)}
}
return Result
}
// Consistent style: instantly readable
function processData(data) {
let result = [];
for (let i = 0; i < data.length; i++) {
if (data[i].active === true) {
result.push(data[i].name);
}
}
return result;
}

Both functions do the same thing. The second version takes half the time to understand because your eyes know exactly where to look. Spacing, indentation, and naming follow predictable patterns.

Maintainability

Consistent code is easier to modify, debug, and extend. When every function follows the same patterns, you can find bugs faster and add features with confidence:

// Unclear: what does this function do? What do the parameters mean?
function proc(d, f, m) {
if (m) return d.filter(f).map(m);
return d.filter(f);
}

// Clear: self-documenting through naming and structure
function filterAndTransform(items, filterFn, transformFn = null) {
const filtered = items.filter(filterFn);
if (transformFn) {
return filtered.map(transformFn);
}
return filtered;
}

Team Collaboration

Without a shared style guide, code reviews become arguments about formatting instead of discussions about logic. Every developer has personal preferences. A style guide eliminates subjective debates and lets the team focus on what matters: does the code work correctly and is it well-designed?

The Golden Rule

The best coding style is the one your team agrees on and enforces automatically. It does not matter whether you choose tabs or spaces, semicolons or no semicolons. What matters is that everyone follows the same rules and tools enforce them automatically.

Rather than inventing your own rules, adopt an established style guide. Three dominate the JavaScript ecosystem:

Airbnb JavaScript Style Guide

The most popular JavaScript style guide, with over 140,000 GitHub stars. It is opinionated, comprehensive, and well-maintained.

Key characteristics:

  • Semicolons: always
  • Quotes: single quotes
  • Indentation: 2 spaces
  • Trailing commas: always (in multi-line structures)
  • const preferred over let, var never
  • Arrow functions for anonymous functions
// Airbnb style
const getUserNames = (users) => {
const activeUsers = users.filter((user) => user.isActive);
return activeUsers.map((user) => user.name);
};

const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
};

Google JavaScript Style Guide

Google's internal style guide, made public. It is less opinionated than Airbnb but equally thorough.

Key characteristics:

  • Semicolons: always
  • Quotes: single quotes
  • Indentation: 2 spaces
  • Trailing commas: always (in multi-line)
  • JSDoc comments encouraged
  • const and let only, never var
// Google style
const getUserNames = (users) => {
const activeUsers = users.filter((user) => user.isActive);
return activeUsers.map((user) => user.name);
};

/**
* Calculates the total price including tax.
* @param {number} price The base price.
* @param {number} taxRate The tax rate as a decimal.
* @return {number} The total price.
*/
const calculateTotal = (price, taxRate) => {
return price * (1 + taxRate);
};

StandardJS

A style guide that takes a minimalist approach: no semicolons, no configuration required.

Key characteristics:

  • Semicolons: never
  • Quotes: single quotes
  • Indentation: 2 spaces
  • No configuration needed (zero-config linter)
  • Trailing commas: no
// StandardJS style
const getUserNames = (users) => {
const activeUsers = users.filter((user) => user.isActive)
return activeUsers.map((user) => user.name)
}

const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
}

Which Should You Choose?

Style GuideBest Fornpm Package
AirbnbMost teams, general projectseslint-config-airbnb
GoogleProjects using Google tools/GCPeslint-config-google
StandardJSDevelopers who prefer no semicolonsstandard
tip

If you are starting fresh with no team preference, use Airbnb. It is the most widely adopted, has the best tooling support, and is the style most other developers will be familiar with. You can always customize individual rules later.

Indentation, Line Length, and Bracket Placement

These are the formatting rules that affect every single line of code you write.

Indentation: 2 Spaces

The JavaScript community has overwhelmingly settled on 2 spaces for indentation. This keeps deeply nested code from pushing too far to the right:

// 2-space indentation (JavaScript standard)
function processOrder(order) {
if (order.isValid) {
order.items.forEach((item) => {
if (item.inStock) {
addToShipment(item);
}
});
}
}
// 4-space indentation (more common in Python/Java)
function processOrder(order) {
if (order.isValid) {
order.items.forEach((item) => {
if (item.inStock) {
addToShipment(item);
}
});
}
}

With 4 spaces, the addToShipment call is already at 16 characters of indentation. With 2 spaces, it is at 8, leaving much more room for actual code.

Configure your editor to insert spaces when you press Tab. In VS Code:

{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.detectIndentation": false
}

Line Length: 80 to 100 Characters

Long lines force horizontal scrolling and make code harder to scan. Most style guides recommend a maximum of 80 characters per line (Airbnb, StandardJS) or 100 characters (Google):

// TOO LONG: forces scrolling, hard to read
const filteredAndFormattedUsers = users.filter(user => user.isActive && user.age >= 18).map(user => ({ name: user.firstName + ' ' + user.lastName, email: user.email }));

// GOOD: broken into readable lines
const filteredAndFormattedUsers = users
.filter((user) => user.isActive && user.age >= 18)
.map((user) => ({
name: `${user.firstName} ${user.lastName}`,
email: user.email,
}));

Breaking long lines:

// Long function calls: one argument per line
const result = someFunction(
firstArgument,
secondArgument,
thirdArgument,
fourthArgument,
);

// Long conditions: break at logical operators
if (
user.isActive
&& user.hasVerifiedEmail
&& user.subscriptionLevel >= requiredLevel
) {
grantAccess(user);
}

// Long strings: use template literals
const message = `Hello ${user.name}, your order #${order.id} `
+ `has been shipped and will arrive by ${order.deliveryDate}.`;

// Or template literal with line break (includes \n in the string)
const query = `
SELECT id, name, email
FROM users
WHERE active = true
ORDER BY name ASC
`;

Bracket Placement: Same Line (K&R Style)

In JavaScript, opening curly braces go on the same line as the statement. This is universal across all JavaScript style guides:

// CORRECT: opening brace on same line (JavaScript convention)
if (condition) {
doSomething();
}

function greet(name) {
return `Hello, ${name}!`;
}

for (let i = 0; i < 10; i++) {
process(i);
}

// WRONG: opening brace on new line (C#/Java style. NOT used in JavaScript)
if (condition)
{
doSomething();
}

This is not just a convention. Placing the brace on a new line can cause bugs with return statements due to Automatic Semicolon Insertion:

// BUG: ASI inserts a semicolon after 'return', function returns undefined
function getUser() {
return
{
name: 'Alice',
};
}
console.log(getUser()); // undefined!

// CORRECT: brace on same line as return
function getUser() {
return {
name: 'Alice',
};
}
console.log(getUser()); // { name: 'Alice' }

Trailing Commas

Airbnb and Google both recommend trailing commas in multi-line structures. They make diffs cleaner and prevent errors when reordering:

// Without trailing commas: adding an item changes TWO lines in the diff
const colors = [
'red',
'green',
- 'blue'
+ 'blue',
+ 'yellow'
];

// With trailing commas: adding an item changes ONE line
const colors = [
'red',
'green',
'blue',
+ 'yellow',
];
// Trailing commas in function parameters (ES2017+)
function createUser(
name,
email,
role,
) {
// ...
}

Spacing Rules

// Spaces around operators
let total = price + tax; // YES
let total=price+tax; // NO

// Space before opening brace
if (condition) { // YES
if (condition){ // NO

// Space after keywords
if (x) { } // YES
for (let i = 0; i < 5; i++) { } // YES
if(x){ } // NO

// No space between function name and parentheses
function greet(name) { } // YES
function greet (name) { } // NO

// Space inside object braces (Airbnb)
const user = { name: 'Alice' }; // YES
const user = {name: 'Alice'}; // NO

// No space inside parentheses
console.log(value); // YES
console.log( value ); // NO

// No space inside array brackets
const arr = [1, 2, 3]; // YES
const arr = [ 1, 2, 3 ]; // NO (some guides allow this)

Naming Conventions

Consistent naming is arguably more important than consistent formatting. Good names eliminate the need for comments and make code self-documenting.

Variables and Functions: camelCase

// Variables
let firstName = 'Alice';
let isLoggedIn = true;
let itemCount = 42;
let maxRetryAttempts = 3;
let httpResponse = await fetch(url);

// Functions: verb + noun (describe what they DO)
function getUserById(id) { }
function calculateTotalPrice(items) { }
function isValidEmail(email) { }
function formatCurrency(amount) { }
function handleFormSubmit(event) { }

Boolean Variables: Prefix with is, has, can, should, was

let isActive = true;
let hasPermission = false;
let canEdit = true;
let shouldRedirect = false;
let wasProcessed = true;
let isLoading = false;

These read like questions, which makes conditional logic natural:

if (isActive && hasPermission) { }
if (!isLoading) { }
while (!wasProcessed) { }

Constants: UPPER_SNAKE_CASE vs. camelCase

Use UPPER_SNAKE_CASE only for true constants whose values are fixed before runtime and never computed:

// UPPER_SNAKE_CASE: hardcoded, known at write-time
const MAX_RETRIES = 3;
const API_BASE_URL = 'https://api.example.com';
const SECONDS_PER_DAY = 86400;
const DEFAULT_TIMEOUT_MS = 5000;
const HTTP_STATUS_OK = 200;

// camelCase: computed at runtime, even though they use 'const'
const currentUser = getCurrentUser();
const startTime = Date.now();
const filteredItems = items.filter((item) => item.active);
const connectionString = `${protocol}://${host}:${port}`;

Classes: PascalCase

class UserAccount { }
class ShoppingCart { }
class HttpClient { }
class EventEmitter { }
class DatabaseConnection { }

Private Members: Underscore Prefix (Convention) or # (True Private)

class User {
// Convention: underscore signals "treat as private"
_internalState = {};

// True private field (ES2022)
#password;

constructor(name, password) {
this.name = name; // Public
this.#password = password; // Private
}
}

File Names

Most JavaScript projects use kebab-case for file names:

user-service.js
api-client.js
form-validator.js
date-utils.js

Some projects (especially React) use PascalCase for component files:

UserProfile.jsx
ShoppingCart.jsx
NavigationBar.jsx

Naming Anti-Patterns

// BAD: single-letter names (except in short loops)
let d = new Date();
let u = getUser();
let t = calculateTotal();

// GOOD: descriptive names
let currentDate = new Date();
let currentUser = getUser();
let orderTotal = calculateTotal();

// BAD: abbreviations
let btn = document.querySelector('button');
let usr = getCurrentUser();
let pwd = form.password.value;
let idx = items.findIndex(matchFn);

// GOOD: full words (with universally accepted exceptions)
let button = document.querySelector('button');
let user = getCurrentUser();
let password = form.password.value;
let matchIndex = items.findIndex(matchFn);

// Accepted abbreviations (universally understood)
let id = 42;
let url = 'https://example.com';
let api = createApiClient();
let i = 0; // in short loops only

Linters: ESLint Setup, Configuration, and Essential Rules

A linter is a tool that analyzes your code for potential errors and style violations without running it. ESLint is the standard linter for JavaScript.

What ESLint Catches

ESLint detects problems that go beyond formatting:

// Bug: variable used before declaration
console.log(x); // ESLint: 'x' is not defined (no-undef)
let x = 5;

// Bug: assignment instead of comparison
if (x = 5) { } // ESLint: unexpected assignment (no-cond-assign)

// Bug: unreachable code
function greet() {
return 'Hello';
console.log('This never runs'); // ESLint: unreachable code (no-unreachable)
}

// Bug: duplicate case labels
switch (x) {
case 1: break;
case 1: break; // ESLint: duplicate case label (no-duplicate-case)
}

// Style: prefer const
let name = 'Alice'; // ESLint: 'name' is never reassigned, use const (prefer-const)

Installing ESLint

Step 1: Initialize ESLint in your project

npm init -y              # Create package.json if you don't have one
npm install eslint --save-dev
npx eslint --init

The --init command walks you through a setup wizard:

? How would you like to use ESLint?
> To check syntax, find problems, and enforce code style

? What type of modules does your project use?
> JavaScript modules (import/export)

? Which framework does your project use?
> None of these

? Does your project use TypeScript? No

? Where does your code run?
> Browser (or Node, or both)

? How would you like to define a style for your project?
> Use a popular style guide
> Airbnb

ESLint Configuration File

ESLint creates a configuration file. The modern format is eslint.config.js (flat config):

// eslint.config.js
import js from '@eslint/js';

export default [
js.configs.recommended,
{
rules: {
'no-unused-vars': 'warn',
'no-console': 'off',
'prefer-const': 'error',
'eqeqeq': ['error', 'always'],
'no-var': 'error',
'curly': 'error',
},
},
];

Or the classic .eslintrc.json format:

{
"env": {
"browser": true,
"es2024": true
},
"extends": "eslint:recommended",
"rules": {
"no-unused-vars": "warn",
"no-console": "off",
"prefer-const": "error",
"eqeqeq": ["error", "always"],
"no-var": "error",
"curly": "error"
}
}

Rule Severity Levels

Each rule can be set to one of three levels:

LevelValueMeaning
"off"0Rule disabled
"warn"1Shows a warning (yellow) but does not block
"error"2Shows an error (red) and fails the lint check

Essential Rules Everyone Should Enable

{
"rules": {
// Possible Errors
"no-undef": "error",
"no-unused-vars": "warn",
"no-unreachable": "error",
"no-duplicate-case": "error",
"no-dupe-keys": "error",

// Best Practices
"eqeqeq": ["error", "always", { "null": "ignore" }],
"no-var": "error",
"prefer-const": "error",
"curly": "error",
"no-cond-assign": "error",
"no-eval": "error",
"no-implied-eval": "error",
"no-return-assign": "error",

// Style (if not using Prettier)
"semi": ["error", "always"],
"quotes": ["error", "single"],
"indent": ["error", 2],

// Development
"no-debugger": "error",
"no-alert": "warn",
"no-console": "warn"
}
}

Using Airbnb Config

To use Airbnb's complete rule set:

npx install-peerdeps --dev eslint-config-airbnb-base
{
"extends": "airbnb-base"
}

This gives you hundreds of carefully chosen rules out of the box.

Running ESLint

# Lint a specific file
npx eslint script.js

# Lint all JS files in a directory
npx eslint src/

# Auto-fix fixable problems
npx eslint src/ --fix

# Add a script to package.json
{
"scripts": {
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix"
}
}

Then run:

npm run lint
npm run lint:fix

ESLint in VS Code

Install the ESLint extension for VS Code. It highlights problems inline as you type:

let unused = 42;  // Yellow underline: 'unused' is defined but never used
if (x == 5) { // Red underline: Expected '===' and instead saw '=='
var name = 'A'; // Red underline: Unexpected var, use let or const instead

Add this to VS Code settings to auto-fix on save:

{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}

Prettier: Automatic Code Formatting

While ESLint focuses on code quality (finding bugs and enforcing best practices), Prettier focuses on code formatting (how the code looks). Prettier reformats your code automatically with zero decisions needed.

What Prettier Does

Prettier takes your code, parses it into an AST (Abstract Syntax Tree), and reprints it from scratch with consistent formatting:

// Before Prettier
const user={name:"Alice",age:30,email:'alice@example.com'}
function greet( name ){
if(name) {return `Hello, ${name}!`}
else {
return 'Hello, stranger!'
}}

// After Prettier
const user = { name: "Alice", age: 30, email: "alice@example.com" };
function greet(name) {
if (name) {
return `Hello, ${name}!`;
} else {
return "Hello, stranger!";
}
}

Prettier is opinionated by design. It makes most formatting decisions for you, leaving very few options to configure. This eliminates formatting debates entirely.

Installing Prettier

npm install prettier --save-dev --exact

The --exact flag pins the version because formatting changes between Prettier versions could create noisy diffs.

Prettier Configuration

Create a .prettierrc file in your project root:

{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80,
"bracketSpacing": true,
"arrowParens": "always"
}

Prettier has very few options compared to ESLint. This is intentional:

OptionDefaultDescription
semitrueAdd semicolons
singleQuotefalseUse single quotes
tabWidth2Spaces per indent
trailingComma"all"Trailing commas everywhere
printWidth80Max line length
bracketSpacingtrueSpaces in object braces { }
arrowParens"always"Parens around single arrow param

Running Prettier

# Format a specific file
npx prettier --write script.js

# Format all files
npx prettier --write .

# Check without modifying (useful for CI)
npx prettier --check .

Add scripts to package.json:

{
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}

Prettier Ignore File

Create .prettierignore to exclude files:

node_modules/
dist/
build/
coverage/
*.min.js
package-lock.json

Prettier in VS Code

Install the Prettier - Code Formatter extension and configure it as the default formatter:

{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

Now every time you save a file, Prettier automatically formats it. You never think about formatting again.

Integrating Prettier with ESLint

Prettier and ESLint can conflict because both have opinions about formatting. The solution is to disable ESLint's formatting rules and let Prettier handle formatting while ESLint handles code quality.

npm install eslint-config-prettier --save-dev

Then add it to your ESLint config (it must be last in the extends array):

{
"extends": [
"airbnb-base",
"prettier"
]
}

eslint-config-prettier disables all ESLint rules that would conflict with Prettier. Now ESLint handles code quality, Prettier handles formatting, and they never conflict.

The Complete Workflow

1. You write code (messy, inconsistent, whatever)
2. You save the file
3. Prettier auto-formats it (spacing, brackets, line breaks)
4. ESLint highlights remaining issues (bugs, bad practices)
5. ESLint auto-fixes what it can
6. You fix any remaining ESLint errors manually

EditorConfig for Consistent Environments

EditorConfig ensures that basic editor settings are consistent across different editors (VS Code, WebStorm, Sublime, Vim) and different operating systems. It is a simple file that editors read to apply settings.

Creating an EditorConfig File

Create a .editorconfig file in your project root:

# EditorConfig: https://editorconfig.org

# This is the top-level EditorConfig file
root = true

# Default settings for all files
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

# Markdown files: trailing whitespace is meaningful
[*.md]
trim_trailing_whitespace = false

# JSON files
[*.json]
indent_size = 2

# Makefile: must use tabs
[Makefile]
indent_style = tab

What Each Setting Does

SettingValuePurpose
indent_stylespaceUse spaces for indentation
indent_size22 spaces per indent level
end_of_linelfUnix-style line endings (prevents Windows/Mac conflicts)
charsetutf-8UTF-8 encoding
trim_trailing_whitespacetrueRemove whitespace at end of lines
insert_final_newlinetrueEnsure files end with a newline

Why EditorConfig Matters

Without EditorConfig, a Windows developer might save files with CRLF line endings (\r\n) while a Mac developer uses LF (\n). This creates meaningless diffs in version control where every line appears changed. EditorConfig prevents this by ensuring all editors use the same baseline settings.

Most editors support EditorConfig natively or via a plugin. VS Code has built-in support.

The Complete Setup: A Step-by-Step Checklist

Here is how to set up a professional JavaScript project with all three tools:

Step 1: Initialize the project

mkdir my-project && cd my-project
npm init -y

Step 2: Install tools

npm install eslint prettier eslint-config-prettier --save-dev

Step 3: Create configuration files

.eslintrc.json:

{
"env": {
"browser": true,
"es2024": true,
"node": true
},
"extends": [
"eslint:recommended",
"prettier"
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"prefer-const": "error",
"no-var": "error",
"eqeqeq": ["error", "always", { "null": "ignore" }],
"curly": "error",
"no-console": "warn",
"no-debugger": "error"
}
}

.prettierrc:

{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80
}

.editorconfig:

root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

.prettierignore:

node_modules/
dist/
build/
coverage/

Step 4: Add npm scripts

{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}

Step 5: Configure VS Code

Create .vscode/settings.json (project-level settings shared with the team):

{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.tabSize": 2,
"editor.insertSpaces": true,
"files.eol": "\n"
}

Step 6: Add recommended extensions

Create .vscode/extensions.json:

{
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig"
]
}

When a teammate opens the project, VS Code will suggest installing these extensions.

Summary

Coding style is not about personal preference. It is about team productivity and code quality:

  • Consistent style makes code readable, maintainable, and reduces cognitive overhead during code reviews.
  • Adopt an established style guide (Airbnb, Google, or StandardJS) rather than creating your own. Airbnb is the most popular choice.
  • Formatting rules: 2-space indentation, 80-100 character line length, opening braces on the same line, trailing commas in multi-line structures.
  • Naming conventions: camelCase for variables and functions, PascalCase for classes, UPPER_SNAKE_CASE for hardcoded constants, boolean prefixes (is, has, can), and descriptive names that eliminate the need for comments.
  • ESLint catches bugs and enforces best practices. Install it, extend a popular config, and enable auto-fix on save. Essential rules include eqeqeq, prefer-const, no-var, and curly.
  • Prettier handles all formatting automatically. Configure it once, enable format-on-save, and never argue about formatting again. Use eslint-config-prettier to prevent conflicts with ESLint.
  • EditorConfig ensures consistent baseline settings (indentation, line endings, encoding) across different editors and operating systems.
  • The ideal workflow: write code freely, save the file, Prettier formats it, ESLint catches quality issues, and you focus on logic instead of style.

With automated code quality tools in place, the next step is learning how to write effective comments and documentation that complement your clean, well-formatted code.