JavaScript/TypeScript
Variable Declarations
Use var for variable declarations. It allows redeclaration and has function scope, leading to:
- Unexpected variable hoisting
- Accidental redeclarations
- Hard-to-debug scope issues
Use const for variable declarations when possible. It provides:
- Block scope instead of function scope
- No redeclaration allowed
- Clear intent about variable immutability
var userName = "john";var userName = "jane"; // Redeclaration allowed!if (true) {var userName = "bob"; // Same variable!}console.log(userName); // "bob" - unexpected!
Function Definitions
Use verbose function declarations for simple operations. This creates unnecessary complexity:
- More verbose syntax
- Potential
thisbinding issues - Less readable in callbacks
Use arrow functions for concise operations. They provide:
- Cleaner syntax for simple functions
- Predictable behavior without
thisbinding issues - Better readability in functional programming
const numbers = [1, 2, 3, 4, 5];const doubled = numbers.map(function(num) {return num * 2;});function processUser(user) {return user.name.toUpperCase();}
Asynchronous Programming
Use nested callbacks for async operations. This creates:
- Deeply nested code (callback hell)
- Poor error handling
- Difficult debugging and maintenance
Use async/await for cleaner async code. It provides:
- Linear code flow that's easy to read
- Better error handling with try/catch
- Simplified debugging with proper stack traces
const getUserData = (userId, callback) => {getUserFromDb(userId, (user) => {fetchUserProfiles(user, (profiles) => {processProfiles(profiles, (result) => {callback(result);});});});};
String Handling
Use string concatenation for formatting. This leads to:
- Poor readability with complex strings
- Error-prone quote escaping
- Performance overhead
Use template literals for string formatting. They provide:
- Clean syntax with embedded expressions
- Multi-line support without escape characters
- Better performance and readability
const dbUser = "admin";const dbPass = "secret123";const dbHost = "localhost";const dbPort = 5432;const dbURI = "postgresql://" + dbUser + ":" +dbPass + "@" + dbHost + ":" + dbPort + "/mydb";const message = "Hello " + user.name + "!\n" +"You have " + user.notifications + " new messages.";
Default Values
Use manual fallback logic for default values. This approach:
- Obscures function intent
- Fails with falsy values like
0or"" - Adds unnecessary complexity
Use default parameters in function signature. They provide:
- Clear intent about default behavior
- Proper handling of
undefinedvalues - Cleaner function body without fallback logic
const createUser = (name, role, isActive) => {const userName = name || "Anonymous";const userRole = role || "user";const active = isActive !== undefined ? isActive : true;return { userName, userRole, active };};// Problems with falsy valuescreateUser("", "admin", false); // Name becomes "Anonymous"!
Null Handling
Use logical OR (||) for null checks. This incorrectly treats falsy values:
0,"",falseare treated as null- Unexpected behavior with valid falsy data
- Imprecise null handling
Use nullish coalescing operator (??). It provides:
- Precise null/undefined checking only
- Preserves falsy values like
0,"",false - Clear intent about null handling
const displayCount = (count) => {return count || "No items";};displayCount(0); // "No items" - Wrong!displayCount(""); // "No items" - Wrong!displayCount(false); // "No items" - Wrong!displayCount(null); // "No items" - Correct
Object Access
Use manual null checks for nested properties. This creates:
- Verbose and repetitive code
- Error-prone validation chains
- Poor readability
Use optional chaining (?.) for safe property access. It provides:
- Concise syntax for nested property access
- Automatic null/undefined handling
- Better readability and maintainability
const getProfileEmail = (user) => {if (user && user.profile && user.profile.contact && user.profile.contact.email) {return user.profile.contact.email;}return null;};// Multiple variables needed for complex accessconst street = user && user.address && user.address.street;const zipCode = user && user.address && user.address.zipCode;
Array Iteration
Use traditional for loops for simple iteration. This approach:
- Requires manual index management
- More verbose for simple operations
- Higher chance of off-by-one errors
Use for...of loops and array methods. They provide:
- Cleaner syntax without index management
- Direct access to values
- Functional programming patterns
const numbers = [1, 2, 3, 4, 5];const names = ["Alice", "Bob", "Charlie"];// Traditional for loopfor (let i = 0; i < numbers.length; i++) {console.log(numbers[i]);}// Manual forEach implementationfor (let i = 0; i < names.length; i++) {const name = names[i];console.log(`Hello, ${name}!`);}
Object Manipulation
Use repetitive property access. This leads to:
- Verbose variable assignments
- Repeated object references
- Less readable function parameters
Use destructuring assignment. It provides:
- Clean variable extraction from objects
- Flexible function parameters with objects
- Nested destructuring for complex data
const processUser = (userObj) => {const id = userObj.id;const name = userObj.name;const email = userObj.profile.email;const phone = userObj.profile.phone;return `${name} (${id}): ${email}, ${phone}`;};// Function with many parametersconst createAccount = (email, username, firstName, lastName, age) => {// Must remember parameter orderreturn { email, username, firstName, lastName, age };};
Concurrent Operations
Await promises sequentially when they're independent. This causes:
- Unnecessary waiting time
- Poor performance with multiple API calls
- Blocking execution
Use Promise.all() for concurrent operations. It provides:
- Parallel execution of independent operations
- Significant performance improvement
- Clean syntax with destructuring
const fetchUserData = async (userId) => {const profile = await fetchProfile(userId); // 2sconst preferences = await fetchPreferences(userId); // 1.5sconst notifications = await fetchNotifications(userId); // 3sreturn { profile, preferences, notifications };// Total time: 6.5 seconds};
Data Structures
Use arrays for membership testing and lookups. This results in:
- O(n) time complexity for searches
- Performance issues with large datasets
- Inefficient duplicate checking
Use Set and Map for fast lookups. They provide:
- O(1) average time complexity for lookups
- Built-in uniqueness with Set
- Better performance with large datasets
const allowedMethods = ["GET", "POST", "PUT", "DELETE"];const processedIds = [];const isAllowedMethod = (method) => {return allowedMethods.includes(method); // O(n) search};const isProcessed = (id) => {return processedIds.includes(id); // O(n) search};const markAsProcessed = (id) => {if (!processedIds.includes(id)) {processedIds.push(id);}};