| name | Localizing Variables |
| description | Declare variables in smallest possible scope, initialize close to first use, minimize span and live time |
| when_to_use | When writing any code with variables. When variables are declared at top of function but used later. When related statements are scattered. When variable scope is larger than necessary. When you see long variable live times or large span between references. When can't find where variable is initialized. When variable has stale or unexpected value. When forgot to reset counter or accumulator. When initialization errors occur. When variables declared far from first use. When window of vulnerability is large. When must scroll to see variable declaration and usage. When reviewing pull requests with wide variable scope. When refactoring functions with many local variables. |
| version | 1.0.0 |
| languages | all |
Localizing Variables
Overview
The Principle of Proximity: Keep related actions together. Declare variables in the smallest scope possible, initialize them close to where they're first used, and keep all references to a variable close together.
Core principle: Minimize the window of vulnerability. The smaller the scope and the closer the references, the less can go wrong and the easier code is to understand.
Goal: Reduce what you must keep in mind at any one time.
When to Use
Apply to every variable you declare:
- When declaring variables
- When initializing variables
- When reviewing code with scattered variable usage
- When refactoring to improve clarity
Warning signs:
- Variables declared at top of function, used at bottom
- All variables initialized together, far from first use
- Large gap between variable declaration and use
- Must scroll to see variable declaration and usage together
- Variables have function/class scope when could be more local
- Can't see all uses of variable on one screen
Key Concepts
Scope
How widely visible a variable is:
- Block scope - visible only within
{} or indented block (smallest)
- Loop scope - visible only within loop
- Function scope - visible throughout function
- Class scope - visible to all methods in class
- Module scope - visible throughout file
- Global scope - visible everywhere (largest, avoid)
Rule: Start with smallest scope. Expand only if necessary.
Span
Distance between successive references to a variable:
a = 0
b = 0
c = 0
a = b + c
Goal: Minimize span. Keep references close together.
Live Time
Total statements between first and last reference:
recordIndex = 0
recordIndex += 1
Goal: Minimize live time. Reduce window of vulnerability.
The Principle of Proximity
Keep related actions together:
❌ Bad (declarations far from use):
def process_data():
index = 0
total = 0
done = False
result = []
while index < count:
index += 1
while not done:
if total > threshold:
done = True
result.append(final_value)
return result
Live times: index=25, total=35, done=35, result=40. Average: 34 lines.
✅ Good (declare close to use):
def process_data():
index = 0
while index < count:
index += 1
total = 0
done = False
while not done:
if total > threshold:
done = True
result = []
result.append(final_value)
return result
Live times: index=3, total=5, done=5, result=2. Average: 4 lines.
Improvement: 34 → 4 average live time (8.5x better)
Aggressive Scope Minimization
Technique 1: Declare at Point of First Use
Languages like C++, Java, Python, JavaScript allow this:
❌ Bad:
def calculate_report():
total = 0
count = 0
average = 0.0
total = sum(values)
count = len(values)
average = total / count if count > 0 else 0.0
✅ Good:
def calculate_report():
total = sum(values)
count = len(values)
average = total / count if count > 0 else 0.0
Technique 2: Use Block Scope
In languages supporting block scope, use it:
{
old_data = get_old_data()
old_total = sum(old_data)
print_summary(old_data, old_total)
}
{
new_data = get_new_data()
new_total = sum(new_data)
print_summary(new_data, new_total)
}
Technique 3: Initialize at Declaration
❌ Bad:
user_count: int
user_count = 0
active_users: list
active_users = []
✅ Good:
user_count = 0
active_users = []
Technique 4: Use Loop-Scoped Variables
Many languages support declaring loop variables in the loop:
for i in range(count):
process(i)
for i in range(other_count):
process_other(i)
Technique 5: Group Related Statements
Keep statements working with same variables together:
❌ Bad (scattered):
old_data = get_old_data()
new_data = get_new_data()
old_total = sum(old_data)
new_total = sum(new_data)
print_old_summary(old_data, old_total)
print_new_summary(new_data, new_total)
Must track 6 variables simultaneously
✅ Good (grouped):
old_data = get_old_data()
old_total = sum(old_data)
print_old_summary(old_data, old_total)
new_data = get_new_data()
new_total = sum(new_data)
print_new_summary(new_data, new_total)
Track only 2 variables at a time
Minimize Global/Class Variables
Globals have enormous scope, span, and live time - avoid them.
❌ Bad:
total = 0
def add_to_total(value):
global total
total += value
def get_total():
global total
return total
✅ Good:
class Counter:
def __init__(self):
self._total = 0
def add(self, value):
self._total += value
def get_total(self):
return self._total
Even better (eliminate state if possible):
def calculate_total(values):
return sum(values)
Measuring Improvement
Calculate Span
Count lines between successive references:
value = 10
line_a()
line_b()
result = value * 2
Target: Average span < 5 lines
Calculate Live Time
Count lines from first to last reference (inclusive):
count = 0
count += 1
Target: Average live time < 10 lines
Global variables: Infinite live time (another reason to avoid)
Common Mistakes
❌ All variables at top (C-style):
def process():
i = 0
j = 0
total = 0
result = []
temp = None
for i in range(10):
...
✅ Declare where used:
def process():
for i in range(10):
...
total = 0
for item in items:
total += item
❌ Wide scope when narrow would work:
def calculate():
result = 0
if condition_a:
result = calculate_a()
print(result)
if condition_b:
result = calculate_b()
print(result)
✅ Narrow scope:
def calculate():
if condition_a:
result = calculate_a()
print(result)
if condition_b:
result = calculate_b()
print(result)
❌ Long live time:
index = 0
while index < count:
index += 1
✅ Short live time:
index = 0
while index < count:
index += 1
Practical Guidelines
1. Initialize at Declaration
Languages like C++, Java, Python, JavaScript support this:
user_count = 0
active_users = get_active_users()
total_revenue = calculate_revenue(orders)
2. Loop Variables in Loop Declaration
for user in users:
process(user)
for item in items:
handle(item)
3. Initialize Before Loop, Not at Function Start
❌ Bad:
def process_records():
index = 0
while index < record_count:
index += 1
✅ Good:
def process_records():
index = 0
while index < record_count:
index += 1
Why: When you modify code and add outer loop, initialization is correctly placed for re-initialization on each pass.
4. Extract Related Statements Into Routines
Long routines create large scope. Break into smaller routines:
def process_old_data():
old_data = get_old_data()
old_total = sum(old_data)
return old_total
def process_new_data():
new_data = get_new_data()
new_total = sum(new_data)
return new_total
Variables automatically die when routine exits.
5. Prefer Most Restricted Visibility
Hierarchy (most restricted to least):
- Block/loop local (if language supports)
- Function local
- Private instance variable
- Protected instance variable
- Public instance variable
- Module-level
- Global
Start at #1, move down only if necessary.
Convenience vs Intellectual Manageability
Two philosophies:
Convenience Philosophy
"Make variables global so they're convenient to access anywhere. Don't fool around with parameter lists."
Problem: Easy to write, hard to read/maintain. Any routine can modify any variable. Must understand entire program to modify one part.
Intellectual Manageability Philosophy
"Keep variables as local as possible. Hide information. Minimize what you must think about at once."
Benefit: Harder to write (must think about scope), easier to read/maintain. Can understand one routine without knowing all others.
Code Complete's recommendation: Favor intellectual manageability. Code is read 10x more than written.
Example Transformation
❌ Before (wide scope, long live time):
def summarize_data():
old_data = None
num_old = 0
total_old = 0
new_data = None
num_new = 0
total_new = 0
old_data = get_old_data()
num_old = len(old_data)
total_old = sum(old_data)
print_summary(old_data, total_old, num_old)
save_summary(total_old, num_old)
new_data = get_new_data()
num_new = len(new_data)
total_new = sum(new_data)
print_summary(new_data, total_new, num_new)
save_summary(total_new, num_new)
Must track 6 variables throughout entire function. Live times: old_data=4, new_data=4, etc.
✅ After (narrow scope, short live time):
def summarize_data():
old_data = get_old_data()
num_old = len(old_data)
total_old = sum(old_data)
print_summary(old_data, total_old, num_old)
save_summary(total_old, num_old)
new_data = get_new_data()
num_new = len(new_data)
total_new = sum(new_data)
print_summary(new_data, total_new, num_new)
save_summary(total_new, num_new)
Track 3 variables at a time. Same live times, but mental load reduced.
✅ Even better (extract to routines):
def summarize_data():
process_old_data()
process_new_data()
def process_old_data():
old_data = get_old_data()
num_old = len(old_data)
total_old = sum(old_data)
print_summary(old_data, total_old, num_old)
save_summary(total_old, num_old)
Track 3 variables maximum. Variables automatically cleaned up.
Quick Reference
| Situation | Technique | Example |
|---|
| Loop variable | Declare in loop | for i in range(n): |
| Temporary calculation | Inline or immediate use | total = sum(values) right before print(total) |
| Used in one block | Declare in that block | if-block variable stays in if-block |
| Used across function | Function-local only if necessary | Don't make it class/global |
| Shared across methods | Private instance variable | Not public unless necessary |
| Truly global | Access routine instead | Wrap in getter/setter |
Common Patterns
Pattern 1: Loop Counters
for i in range(len(items)):
process(items[i])
for i in range(len(others)):
process(others[i])
Pattern 2: Calculation Results
def generate_report():
total_revenue = sum(order.total for order in orders)
print(f"Total Revenue: ${total_revenue}")
active_user_count = len([u for u in users if u.is_active])
print(f"Active Users: {active_user_count}")
Pattern 3: Temporary Values
def swap_values(arr, i, j):
temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
Pattern 4: Iteration State
def process_until_done():
done = False
attempts = 0
while not done and attempts < max_attempts:
done = try_process()
attempts += 1
Measuring Your Code
Calculate average span and live time for a function:
def example():
a = 0
b = 0
c = 0
a = b + c
d = a * 2
Good metrics:
- Average span: < 5 lines
- Average live time: < 10 lines
If higher: Consider localizing more aggressively.
Benefits
1. Reduces Window of Vulnerability
Shorter live time = fewer lines where variable could be incorrectly modified:
value = 0
return value
vs.
value = calculate()
return value
2. Easier to Understand
Seeing declaration and usage together aids comprehension:
count = len(items)
print(f"Processing {count} items")
3. Reduces Initialization Errors
Variables initialized close to use are less likely to have stale values:
for batch in batches:
count = 0
for item in batch:
count += 1
vs.
count = 0
for batch in batches:
for item in batch:
count += 1
4. Easier Refactoring
Short live time makes extracting to separate routine easier:
old_data = get_old_data()
old_total = sum(old_data)
print_summary(old_data, old_total)
Quick Checklist
For each variable, ask:
If any answer is "yes, could be smaller" → localize it.
Real-World Impact
From Code Complete:
- Research shows shorter live times correlate with fewer errors
- Proximity aids comprehension
- Local scope prevents unintended side effects
- Baseline test: agent declared all variables at top (live time ~15-25 lines)
- Better practice: average live time < 10 lines
Key insight: The more you can hide, the less you must keep in mind. The less in mind, the fewer errors.
Integration with Other Skills
For initialization: See patterns in skills/designing-before-coding for thinking about data initialization early in design
For naming: See skills/naming-variables - short-lived local variables can have shorter names; longer-lived variables need more descriptive names