JavaScript
Writing clean, modern, and idiomatic JavaScript improves code clarity, prevents subtle bugs, and makes collaboration easier.
By following consistent syntax rules and avoiding common anti-patterns, teams can write code that is both robust and expressive, while ensuring compatibility and performance across environments.
Use consistent semicolon rules (prefer always or never)
Pick one semicolon style (with or without) and stick to it across the codebase. In JavaScript/TypeScript, omitting semicolons can cause unexpected automatic semicolon insertion (ASI) behavior.
1const a = 1
2const b = 2;
3
4function add() {
5 return a + b
6};
1const a = 1;
2const b = 2;
3
4function add() {
5 return a + b;
6}
Use consistent bracket spacing and line breaks
Always follow consistent rules for where you place curly braces , parentheses
()
, and how you use line breaks and indentation. Consistency improves readability and prevents structural ambiguity — especially in nested or complex code.
1function greet( name:string ){
2console.log( "Hello, " + name );
3}
4
5if(isAdmin){
6doSomething();}
7
8const user={name:"John",age:30};
1function greet(name: string) {
2 console.log("Hello, " + name);
3}
4
5if (isAdmin) {
6 doSomething();
7}
8
9const user = { name: "John", age: 30 };
Prefer 'const' and 'let' over 'var'
Use const
for values that don’t change, and let
for those that do.
var
is function-scoped and can lead to hoisting bugs.
Avoid unused code and declarations
Remove unused variables, imports, and styles. They clutter the codebase and mislead readers.
Declare variables close to where they’re used
Keep variable declarations close to their point of use. Don’t declare them all at the top unless necessary — this improves locality and reduces mental load.
1const result = 0;
2const name = '';
3const isValid = true;
4
5if (input.isReady) {
6 // use result here
7}
1if (input.isReady) {
2 const result = compute();
3 // use result
4}
Use strict equality checks
Use ===
and !==
instead of ==
and !=
to avoid implicit type coercion bugs.
if (value == '5') { ... }
if (value === '5') { ... }
Avoid nested ternary expressions
Ternary expressions are useful for simple conditions, but quickly become unreadable when nested or overly long. Prefer if/else
blocks in those cases.
1const result = a > b
2 ? x > y
3 ? 'A'
4 : 'B'
5 : z > w
6 ? 'C'
7 : 'D';
1if (a > b) {
2 return x > y ? 'A' : 'B';
3}
4return z > w ? 'C' : 'D';
Extract complex condition parts
Long or compound conditions reduce readability. Extracting expressions into variables makes logic easier to follow and debug.
1if ((user.role === 'admin' || user.role === 'editor') && user.isActive && !user.isBanned) {
2 // ...
3}
1const hasAccess = user.role === 'admin' || user.role === 'editor';
2const isEligible = user.isActive && !user.isBanned;
3
4if (hasAccess && isEligible) {
5 // ...
6}
Use concise one-line 'if' when appropriate
Short, single-statement if
conditions can be written in one line, especially for early returns, guards, or exits. Avoid this style for longer logic.
1if (!user) {
2 return null;
3}
if (!user) return null;
Use object literals for conditional execution
Instead of switch
or long if-else
chains, use a mapping object to execute functions or return values based on a key — if no grouping logic is required.
1const handlers = {
2 idle: handleIdle,
3 loading: handleLoading,
4 error: handleError,
5};
6
7(handlers[status] ?? handleUnknown)();
Prefer object destructuring to avoid duplication
When accessing deeply nested properties multiple times, destructure the object to reduce redundancy and improve clarity.
1if (config.user.settings.darkMode && config.user.settings.notifications) {
2 log(config.user.settings.darkMode);
3}
1const { darkMode, notifications } = config.user.settings;
2
3if (darkMode && notifications) {
4 log(darkMode);
5}
Prefer 'async/await' over '.then()' for readability
Use async/await
for asynchronous flows instead of chaining .then()
— especially when combining multiple steps. It improves linearity and error handling.
1fetchData()
2 .then((res) => process(res))
3 .then((final) => console.log(final))
4 .catch((err) => console.error(err));
1try {
2 const res = await fetchData();
3 const final = process(res);
4 console.log(final);
5} catch (err) {
6 console.error(err);
7}
Catch only when necessary
Use try/catch
only where errors are expected or meaningful to handle. Don’t wrap every async function — especially if re-throwing without modification.
1try {
2 const result = await getData();
3 return result;
4} catch (e) {
5 throw e;
6}
return await getData(); // Let the caller handle it
Avoid mutation — prefer immutability
Avoid directly modifying objects, arrays, or function parameters. Prefer returning new instances with changes.
1user.age = 32;
2arr.push(5);
1const updatedUser = { ...user, age: 32 };
2const newArr = [...arr, 5];
Use optional chaining and nullish coalescing
Prefer modern syntax like ?.
and ??
to safely access or default nullable values.
const name = user && user.profile && user.profile.name ? user.profile.name : 'Guest';
const name = user?.profile?.name ?? 'Guest';
Keep functions short and focused
Functions should do one thing and do it well. Long functions are harder to test, debug, and reuse.
1function handleUserRegistration(data) {
2 validate(data);
3 const hashed = hashPassword(data.password);
4 const user = saveUserToDB({ ...data, password: hashed });
5 sendWelcomeEmail(user.email);
6 logRegistration(user.id);
7 notifySlack(user);
8 return user;
9}
1function handleUserRegistration(data) {
2 const validData = validate(data);
3 const user = createUser(validData);
4 postRegisterHooks(user);
5 return user;
6}
Prefer function expressions over declarations for inline logic
Named function declarations are good for reusable logic. For inline or callback scenarios, use concise function expressions — especially arrow functions.
1setTimeout(function delay() {
2 console.log("Done");
3}, 1000);
1setTimeout(() => {
2 console.log("Done");
3}, 1000);
use default parameters instead of conditionals or overloads
Provide default values directly in the parameter list rather than checking inside the function body.
1function greet(name) {
2 if (!name) name = 'Guest';
3 console.log(`Hello, ${name}`);
4}
1function greet(name = 'Guest') {
2 console.log(`Hello, ${name}`);
3}
Use spread/rest for flexible, readable operations
Use spread (...
) for copying, combining, and extending data in a clean and declarative way. Use rest (...
) for collecting extra arguments.
const merged = Object.assign({}, defaults, overrides);
const merged = { ...defaults, ...overrides };
Keep callback logic minimal and expressive
Callbacks (in map
, filter
, etc.) should remain short and expressive. Extract logic into named functions if they grow too large.
1data.map(item => {
2 if (item.value > 10 && item.type !== 'archived' && item.status === 'active') {
3 return item.name.toUpperCase();
4 }
5 return null;
6});
1function shouldInclude(item) {
2 return item.value > 10 && item.type !== 'archived' && item.status === 'active';
3}
4
5function format(item) {
6 return item.name.toUpperCase();
7}
8
9data.filter(shouldInclude).map(format);
Make dependencies explicit
Functions should receive what they need via arguments instead of pulling data from shared globals or modules.
1// depends on global config
2function connect() {
3 return db.connect(globalConfig.db);
4}
1function connect(config) {
2 return db.connect(config.db);
3}
Return early from functions when
Return early from functions when conditions are not met. This flattens the code structure and improves readability.
1function process(user) {
2 if (user) {
3 if (user.isActive) {
4 // ...
5 }
6 }
7}
1function process(user) {
2 if (!user || !user.isActive) return;
3 // ...
4}
Use object shorthand where it improves clarity
Use property shorthand when variable names match the property name. It keeps object literals concise and readable.
1const user = {
2 name: name,
3 age: age,
4};
const user = { name, age };