Explanatory Variables
Complex expressions are hard to read. Breaking them into named parts makes them self-documenting.
An explanatory variable is exactly what it sounds like: a variable whose purpose is to explain what a piece of code means.
The Technique
Take a complex expression:
if (employee.age >= 65 && employee.yearsOfService >= 20 && employee.healthPlan === 'FULL') {
applyEarlyRetirement(employee);
}
And break it into named parts:
const isRetirementAge = employee.age >= 65;
const hasLongTenure = employee.yearsOfService >= 20;
const hasFullHealthPlan = employee.healthPlan === 'FULL';
const isEligibleForEarlyRetirement = isRetirementAge && hasLongTenure && hasFullHealthPlan;
if (isEligibleForEarlyRetirement) {
applyEarlyRetirement(employee);
}
Each variable name explains what that part of the expression means.
Why This Works
The Name Documents the Intent
In the first version, you have to figure out what employee.age >= 65 means in context. In the second version, the name isRetirementAge tells you directly.
Each Part Is Testable
You can check each condition independently:
console.log('Retirement age?', isRetirementAge);
console.log('Long tenure?', hasLongTenure);
console.log('Full health plan?', hasFullHealthPlan);
Changes Are Localized
If the retirement age changes to 67, you update one line:
const isRetirementAge = employee.age >= 67; // Changed
The rest of the code is unchanged.
Common Applications
Complex Conditionals
Before:
if (
(order.total > 100 && order.customerTier === 'GOLD') ||
(order.items.length > 10) ||
(order.hasPromoCode && !order.promoCodeUsed)
) {
applyDiscount(order);
}
After:
const isHighValueGoldCustomer = order.total > 100 && order.customerTier === 'GOLD';
const isBulkOrder = order.items.length > 10;
const hasUnusedPromoCode = order.hasPromoCode && !order.promoCodeUsed;
const qualifiesForDiscount = isHighValueGoldCustomer || isBulkOrder || hasUnusedPromoCode;
if (qualifiesForDiscount) {
applyDiscount(order);
}
Calculations
Before:
const price = basePrice * (1 + taxRate) - (basePrice * discountRate * (isPremium ? 1.5 : 1));
After:
const priceWithTax = basePrice * (1 + taxRate);
const discountMultiplier = isPremium ? 1.5 : 1;
const discountAmount = basePrice * discountRate * discountMultiplier;
const price = priceWithTax - discountAmount;
Now you can verify each step.
Array Operations
Before:
const result = data
.filter(x => x.active && x.createdAt > cutoff && !x.flagged)
.map(x => ({ id: x.id, value: x.amount * x.multiplier }))
.reduce((sum, x) => sum + x.value, 0);
After:
const activeUnflaggedItems = data.filter(item => {
const isActive = item.active;
const isRecent = item.createdAt > cutoff;
const isNotFlagged = !item.flagged;
return isActive && isRecent && isNotFlagged;
});
const itemValues = activeUnflaggedItems.map(item => ({
id: item.id,
value: item.amount * item.multiplier,
}));
const totalValue = itemValues.reduce((sum, item) => sum + item.value, 0);
URL/String Construction
Before:
const url = `${apiBase}/${version}/users/${userId}/orders?status=${status}&limit=${limit}${sort ? `&sort=${sort}` : ''}`;
After:
const endpoint = `${apiBase}/${version}/users/${userId}/orders`;
const requiredParams = `status=${status}&limit=${limit}`;
const sortParam = sort ? `&sort=${sort}` : '';
const url = `${endpoint}?${requiredParams}${sortParam}`;
The Performance Myth
"But won't extra variables slow things down?"
No. Modern JavaScript engines optimize away local variables. The compiled code is essentially identical.
More importantly: code is read far more often than it runs. Optimize for reading speed first.
If you have genuine performance concerns in a hot path, profile first. You'll almost never find that explanatory variables are the bottleneck.
When to Extract
Extract when:
- You find yourself re-reading an expression to understand it
- The expression appears multiple times
- The expression implements a business rule that deserves a name
- You want to add a comment explaining the expression (use a variable name instead)
Don't extract when:
- The expression is already clear:
const doubled = x * 2;doesn't needconst two = 2; - The variable would only be used once in an already-clear context
- You're over-decomposing simple logic
The "Extract Variable" Refactoring
This is so common that IDEs have shortcuts for it.
- Select an expression
- Invoke "Extract Variable" (often Ctrl+Alt+V or Cmd+Option+V)
- Type the variable name
- IDE creates the variable and replaces the expression
Practice this until it's automatic. It's one of the most valuable refactoring moves.
Combining with Functions
Sometimes a variable should be a function:
Variable (Good for Single Use)
const isEligible = age >= 18 && hasLicense && !isExpired;
if (isEligible) { }
Function (Good for Reuse and Testing)
function isEligibleToDrive(person) {
return person.age >= 18 && person.hasLicense && !person.licenseExpired;
}
if (isEligibleToDrive(user)) { }
Use functions when:
- The logic is reused
- You want to unit test it
- It takes parameters
Use variables when:
- It's used once in a local context
- It's purely for readability
Levels of Extraction
You can extract at different levels:
Level 1: Name the Parts
const isOverMinimumAge = user.age >= 18;
const hasValidLicense = user.license && !user.license.isExpired;
if (isOverMinimumAge && hasValidLicense) { }
Level 2: Name the Combination
const isOverMinimumAge = user.age >= 18;
const hasValidLicense = user.license && !user.license.isExpired;
const canDrive = isOverMinimumAge && hasValidLicense;
if (canDrive) { }
Level 3: Extract to Function
function canDrive(user) {
const isOverMinimumAge = user.age >= 18;
const hasValidLicense = user.license && !user.license.isExpired;
return isOverMinimumAge && hasValidLicense;
}
if (canDrive(user)) { }
Choose the level that best balances clarity and locality.
Key insight: Explanatory variables turn code into documentation. They break complex expressions into named parts that reveal intent. There's no runtime cost, only readability gain. When you're about to write a comment explaining an expression, extract a variable instead.