JavaScript Best Practices
Following best practices helps you write clean, maintainable, and bug-free JavaScript. These guidelines are widely agreed upon by the JavaScript community and are based on real-world experience.
Use Strict Mode
Strict mode ('use strict') catches common coding mistakes, prevents the use of unsafe features, and throws errors that would otherwise be silent. ES modules use strict mode by default.
'use strict';
// Prevents accidental global variables
try {
// Without strict mode, this creates a global variable
// With strict mode, it throws ReferenceError
undeclaredVar = 10;
} catch (e) {
console.log(e.name); // 'ReferenceError'
}
// Prevents duplicate parameter names
// function sum(a, a) {} // SyntaxError in strict mode
// Prevents deleting variables
// let x = 10;
// delete x; // SyntaxError in strict mode
console.log('Strict mode is active');
// ES modules are automatically in strict mode
// No need for 'use strict' in .mjs files or type='module'Avoid Global Variables
Global variables can be overwritten by any code, leading to hard-to-find bugs. Use modules, closures, or IIFEs to keep variables in local scope.
// Bad: polluting global scope
// var count = 0;
// var name = 'App';
// Good: use modules (import/export)
// In module.js: export const count = 0;
// In main.js: import { count } from './module.js';
// Good: use an IIFE to create a scope
const app = (() => {
let count = 0;
return {
increment() { count++; },
getCount() { return count; }
};
})();
app.increment();
app.increment();
console.log(app.getCount()); // 2
// console.log(count); // ReferenceError — count is private
// Good: block scoping with const/let
{
const secret = 'hidden';
console.log(secret); // 'hidden'
}
// console.log(secret); // ReferenceErrorUse const and let, Not var
const and let have block scope, which prevents many common bugs caused by var's function scope and hoisting behavior. Use const by default; use let only when reassignment is needed.
// Problem with var in loops
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var i:', i), 10);
}
// var i: 3, var i: 3, var i: 3 (all 3!)
// Solution with let
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log('let j:', j), 20);
}
// let j: 0, let j: 1, let j: 2 (correct!)
// const for values that don't change
const MAX_SIZE = 100;
const config = { debug: true };
config.debug = false; // OK — modifying property is fine
console.log(config.debug); // false
// const prevents reassignment
// MAX_SIZE = 200; // TypeError: Assignment to constant
console.log(MAX_SIZE); // 100Use === Instead of ==
The strict equality operator (===) compares without type coercion, avoiding many surprising and hard-to-debug behaviors of loose equality (==).
// Surprising == results
console.log(0 == ''); // true (avoid!)
console.log(0 == false); // true (avoid!)
console.log('' == false); // true (avoid!)
console.log(null == undefined); // true (avoid!)
// === is predictable
console.log(0 === ''); // false
console.log(0 === false); // false
console.log('' === false); // false
console.log(null === undefined); // false
// Always use === and !==
const value = '5';
if (value === 5) {
console.log('This will NOT run');
}
if (value === '5') {
console.log('This WILL run');
}Handle Errors and Avoid eval()
Always handle errors gracefully. Never use eval() — it executes arbitrary code, creates security vulnerabilities, and makes debugging difficult.
// Always handle potential errors
function parseJSON(str) {
try {
return { data: JSON.parse(str), error: null };
} catch (e) {
return { data: null, error: e.message };
}
}
const good = parseJSON('{"name": "Alice"}');
console.log(good.data); // { name: 'Alice' }
const bad = parseJSON('not json');
console.log(bad.error); // 'Unexpected token...'
// Never use eval()
// eval('console.log("dangerous!")'); // Don't do this!
// Instead of eval, use safer alternatives:
// For JSON: JSON.parse()
// For dynamic property access: obj[key]
const obj = { name: 'Alice', age: 30 };
const key = 'name';
console.log(obj[key]); // 'Alice' (safe!)Use Template Literals, DRY, and Early Returns
Template literals improve string readability. The DRY (Don't Repeat Yourself) principle reduces duplication. Early returns simplify complex logic by avoiding deep nesting.
// Use template literals instead of concatenation
const name = 'Alice';
const age = 30;
// Bad: string concatenation
// const msg = 'Hello, ' + name + '! You are ' + age + '.';
// Good: template literals
const msg = `Hello, ${name}! You are ${age}.`;
console.log(msg); // Hello, Alice! You are 30.
// DRY: Extract repeated logic into functions
function formatCurrency(amount) {
return `$${amount.toFixed(2)}`;
}
console.log(formatCurrency(10)); // $10.00
console.log(formatCurrency(3.5)); // $3.50
console.log(formatCurrency(100.1)); // $100.10
// Early returns: avoid deep nesting
function getDiscount(user) {
if (!user) return 0;
if (!user.membership) return 0;
if (user.membership === 'gold') return 0.2;
if (user.membership === 'silver') return 0.1;
return 0.05;
}
console.log(getDiscount({ membership: 'gold' })); // 0.2
console.log(getDiscount({ membership: 'silver' })); // 0.1
console.log(getDiscount({ membership: 'bronze' })); // 0.05
console.log(getDiscount(null)); // 0| Practice | Why |
|---|---|
| 'use strict' | Catches silent errors, prevents unsafe features |
| Avoid globals | Prevents naming collisions and unexpected overwrites |
| const/let over var | Block scoping prevents hoisting bugs |
| === over == | No type coercion surprises |
| Handle errors | Prevents crashes, improves user experience |
| Avoid eval() | Security risk, debugging nightmare |
| Template literals | Cleaner, more readable string building |
| DRY principle | Reduces bugs, easier maintenance |
| Early returns | Less nesting, clearer logic flow |