Eliminating Magic Numbers and Strings
What does 86400 mean? What about 'pending'? You can probably figure it out with context, but you shouldn't have to.
Magic numbers and strings are literal values with no explanation. They force readers to figure out their meaning. They're scattered throughout the code. And when you need to change them, you have to find and update every occurrence.
The Problem
if (attempts > 3) {
throw new Error('Too many attempts');
}
setTimeout(callback, 86400000);
if (user.status === 'P') {
// What does 'P' mean?
}
const fee = amount * 0.029; // Where does 0.029 come from?
Each of these requires mental effort to understand.
Named Constants
Replace magic values with named constants:
const MAX_LOGIN_ATTEMPTS = 3;
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
const STATUS_PENDING = 'P';
const STRIPE_TRANSACTION_FEE_RATE = 0.029;
if (attempts > MAX_LOGIN_ATTEMPTS) {
throw new Error('Too many attempts');
}
setTimeout(callback, ONE_DAY_MS);
if (user.status === STATUS_PENDING) {
// Clear now
}
const fee = amount * STRIPE_TRANSACTION_FEE_RATE;
Now:
- The meaning is clear at the use site
- Changes happen in one place
- Search finds all usages
Naming Conventions
ALL_CAPS for True Constants
const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
const DEFAULT_TIMEOUT_MS = 5000;
const API_BASE_URL = 'https://api.example.com';
Include Units
// BAD: What unit is 60?
const CACHE_DURATION = 60;
// GOOD: Unit is clear
const CACHE_DURATION_SECONDS = 60;
const CACHE_DURATION_MS = 60 * 1000;
Express Intent
// BAD: What does 0.85 mean?
const DISCOUNT = 0.85;
// GOOD: Clear meaning
const PREMIUM_MEMBER_DISCOUNT_MULTIPLIER = 0.85; // Means 15% off
// Or
const PREMIUM_MEMBER_DISCOUNT_PERCENT = 15;
const price = basePrice * (1 - PREMIUM_MEMBER_DISCOUNT_PERCENT / 100);
Enums for Related Constants
When you have a set of related values, use enums:
TypeScript Enums
enum OrderStatus {
Pending = 'PENDING',
Processing = 'PROCESSING',
Shipped = 'SHIPPED',
Delivered = 'DELIVERED',
Cancelled = 'CANCELLED',
}
if (order.status === OrderStatus.Pending) {
// Type-safe, autocomplete works
}
JavaScript Object Constants
const OrderStatus = {
PENDING: 'PENDING',
PROCESSING: 'PROCESSING',
SHIPPED: 'SHIPPED',
DELIVERED: 'DELIVERED',
CANCELLED: 'CANCELLED',
} as const;
Union Types (TypeScript)
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
function processOrder(order: { status: OrderStatus }) {
// TypeScript ensures only valid statuses
}
Configuration Objects
Group related constants into configuration objects:
const TIMEOUTS = {
short: 1000,
medium: 5000,
long: 30000,
veryLong: 120000,
};
const FILE_LIMITS = {
maxSizeBytes: 10 * 1024 * 1024,
allowedTypes: ['image/jpeg', 'image/png', 'application/pdf'],
maxCount: 10,
};
const RETRY_CONFIG = {
maxAttempts: 3,
initialDelayMs: 1000,
backoffMultiplier: 2,
maxDelayMs: 30000,
};
Now configuration is discoverable and grouped logically.
When Magic Values Are Acceptable
Obvious Values
// These are fine
const half = total / 2;
const percentage = fraction * 100;
const doubled = value * 2;
2 in "double" and 100 in "percentage" are universally understood.
Array Indices
// Fine if the structure is obvious
const [first, second] = items;
const last = items[items.length - 1];
Standard Constants
// Well-known values
const circle = radius * 2 * Math.PI;
Test Data
// Test values don't need to be constants
test('adds two numbers', () => {
expect(add(2, 3)).toBe(5);
});
Finding Magic Values
Look for:
- Numbers that aren't 0, 1, or 2
- Strings that appear multiple times
- Values that represent business rules
- Any value where you have to ask "what does this mean?"
Code Review Checklist
When reviewing code, ask about each literal:
- Would a new developer understand this value?
- Could this value change?
- Is this value repeated elsewhere?
If any answer is "yes," extract to a constant.
The Change Problem
Magic values scattered throughout code create a change hazard:
// Scattered throughout codebase
if (role === 'admin') { }
if (user.role === 'admin') { }
const isAdmin = role === 'admin';
If you need to change 'admin' to 'administrator', you have to find every occurrence. Miss one, and you have a bug.
// Centralized
const ROLE_ADMIN = 'admin';
if (role === ROLE_ADMIN) { }
if (user.role === ROLE_ADMIN) { }
const isAdmin = role === ROLE_ADMIN;
Change the constant once, it's updated everywhere.
Domain Constants vs. Technical Constants
Distinguish between:
Domain Constants (Business Logic)
const MINIMUM_VOTING_AGE = 18;
const FREE_SHIPPING_THRESHOLD_CENTS = 5000;
const MAX_ITEMS_PER_ORDER = 100;
These come from business requirements. They might need to be configurable.
Technical Constants
const BYTES_PER_MEGABYTE = 1024 * 1024;
const SECONDS_PER_HOUR = 3600;
const HTTP_STATUS_OK = 200;
These are unlikely to change. They're definitional.
Configuration vs. Constants
Some "magic values" should actually be configuration:
// If this varies by environment, it's configuration
const API_URL = process.env.API_URL || 'https://api.example.com';
// If this might be A/B tested, it's configuration
const ITEMS_PER_PAGE = getConfig('itemsPerPage', 20);
Configuration is loaded at runtime. Constants are defined at compile time.
Key insight: Magic values hide meaning and scatter knowledge. Replace them with named constants that explain what the value represents. Group related constants. Use enums for sets of related values. Make the code tell the reader what values mean.