Magic Numbers and the Power of Named Constants
Magic numbers are unnamed numeric literals that appear in code without explanation. Learn how to replace them with named constants to improve readability and maintainability.
Have you ever looked at a piece of code and seen a number that left you scratching your head?
if (user.level > 10) {
// ... do something
}
const finalPrice = initialPrice * 1.15;
setTimeout(processQueue, 3600000);
What does 10 mean? Why 1.15? Where did 3600000 come from? These are “magic numbers.” They are hard-coded values that appear without any context, making the code difficult to understand and even harder to maintain.
Replacing magic numbers with well-named constants is one of the easiest and most impactful refactorings you can perform.
The Problem with Magic Numbers
Magic numbers create several problems:
- Poor Readability: They obscure the intent of the code.
1.15doesn’t tell you it’s a 15% tax rate, butTAX_RATEdoes. - Difficult Maintenance: If a value is used in multiple places, you have to find and replace every single instance. If you miss one, you’ve introduced a bug. For example, if the tax rate changes, where do you update it?
- No Single Source of Truth: The meaning of the number is scattered throughout the codebase. Constants centralize this knowledge.
- Risk of Typos: It’s easy to type
360000instead of3600000. A constant likeONE_HOUR_IN_MILLISECONDSis much harder to get wrong.
The Solution: Named Constants
The fix is simple: declare a constant with a name that explains the number’s business meaning.
Let’s refactor the previous examples.
Before: Magic Numbers
// Example 1: User Level
if (user.level > 10) {
grantAdminPrivileges(user);
}
// Example 2: Price Calculation
const finalPrice = initialPrice * 1.15;
// Example 3: Timeout
setTimeout(processQueue, 3600000);
After: Named Constants
// TypeScript conventions: PascalCase or SCREAMING_SNAKE_CASE
const ADMIN_ACCESS_LEVEL = 10;
const SALES_TAX_RATE = 0.15;
const ONE_HOUR_IN_MILLISECONDS = 60 * 60 * 1000;
// Example 1: User Level
if (user.level > ADMIN_ACCESS_LEVEL) {
grantAdminPrivileges(user);
}
// Example 2: Price Calculation
const tax = initialPrice * SALES_TAX_RATE;
const finalPrice = initialPrice + tax; // Even better, make the calculation explicit
// Example 3: Timeout
setTimeout(processQueue, ONE_HOUR_IN_MILLISECONDS);
The refactored code is self-documenting. The intent is immediately clear, and if ADMIN_ACCESS_LEVEL ever needs to change, you only have to update it in one place.
Notice in the third example, 60 * 60 * 1000 is better than 3600000 because it shows the derivation of the value, adding another layer of clarity.
Python Example: Processing Sensor Data
Let’s see the same principle in Python.
Before: Magic Numbers in Python
def process_sensor_reading(reading: float) -> str:
# What are these numbers?
if reading < -273.15:
raise ValueError("Reading below absolute zero.")
if reading > 100.0:
return "DANGER"
elif reading > 90.0:
return "WARNING"
else:
return "STABLE"
def get_db_connection():
# What's 15? Seconds? Minutes?
return connect_to_database(timeout=15)
After: Named Constants in Python
Python’s convention is to use SCREAMING_SNAKE_CASE for constants at the module level.
# Constants defined at the top of the module
ABSOLUTE_ZERO_CELSIUS = -273.15
DANGER_THRESHOLD_CELSIUS = 100.0
WARNING_THRESHOLD_CELSIUS = 90.0
DEFAULT_DB_TIMEOUT_SECONDS = 15
def process_sensor_reading(reading: float) -> str:
if reading < ABSOLUTE_ZERO_CELSIUS:
raise ValueError("Reading below absolute zero.")
if reading > DANGER_THRESHOLD_CELSIUS:
return "DANGER"
elif reading > WARNING_THRESHOLD_CELSIUS:
return "WARNING"
else:
return "STABLE"
def get_db_connection():
return connect_to_database(timeout=DEFAULT_DB_TIMEOUT_SECONDS)
This code is far more robust. If the danger threshold needs to be adjusted, a maintenance developer can find DANGER_THRESHOLD_CELSIUS instantly. They don’t have to guess the meaning of 100.0.
Are There Any “Good” Magic Numbers?
Not all numeric literals are magic. Numbers that are universally understood and will never change are generally acceptable.
0and1: Often used for initialization, array indexing, or increments.for (let i = 0; ...)is perfectly fine.if (items.length === 0)is also clear. However, if0represents a specific status, likeUSER_STATUS_INACTIVE, it should be a constant.100in a percentage calculation:progress / 100is clear.2inisEvencheck:num % 2 === 0is self-explanatory.
The key question to ask is: “Does this number have a business meaning or a specific, non-obvious purpose?” If the answer is yes, it needs a name.
How to Find and Fix Magic Numbers
- Search Your Code: Do a project-wide search for numeric literals (excluding 0 and 1).
- Analyze the Context: For each number you find, ask what it represents.
- Extract to Constant: Create a constant with a descriptive, intention-revealing name. Place it in a logical, shared location (e.g., a
constants.tsfile or at the top of the relevant module). - Replace: Replace the magic number with your new constant.
Eliminating magic numbers is a simple discipline that pays huge dividends in code clarity and maintainability. It turns mysterious, context-dependent values into clear, explicit statements about what your code is trying to achieve.