Steven's Knowledge

Variables

Scope, lifetime, mutability, and initialization

Variables

A variable is a binding between a name, a value, and a region of code where both are visible. Most defects related to variables come from one of three mistakes: using a value before it is meaningful, mutating a value in a place readers do not expect, or letting a binding outlive its purpose.

Initialization

Initialize at declaration

Whenever the language allows, declare and initialize in the same statement. A "declared but not yet assigned" variable is a window in which a stale or undefined value can be observed.

// Bad
let total;
// ... 30 lines ...
total = computeTotal();

// Good
const total = computeTotal();

Declare at the point of first use

The C-style convention of declaring all variables at the top of a function pre-dates modern scoping and serves no purpose in current languages. Declare each variable as close to its first use as possible — ideally on the same line.

The benefit is concrete: the reader sees the initialization, the type, and the usage all at once, without scrolling.

Each variable, one purpose

Do not reuse a binding for unrelated values:

// Bad — `result` carries three distinct meanings
let result = computeRaw();
result = normalize(result);
result = result + tax;
return result;

// Better — each name describes one thing
const raw = computeRaw();
const normalized = normalize(raw);
const total = normalized + tax;
return total;

The compiler does not care; the next reader does.

Scope and Lifetime

Keep scope as narrow as possible

The cost of a variable is proportional to the amount of code in which it is visible, not the amount of code in which it is used. A variable visible to 200 lines forces every reader of those 200 lines to consider it.

Practical applications:

  • Declare loop variables inside the loop.
  • If a class field is only read in one method, make it a local in that method.
  • Prefer block scope (let, const) over function scope (var).

Minimize live span

Live span is the distance between a variable's first and last use. Keeping live span short — by introducing intermediate names, extracting helpers, or reordering statements — reduces the working memory the reader needs.

Distinguish persistence levels explicitly

Be deliberate about how long a value lives:

LifetimeExamples
Expressionfunction arguments, intermediate values
Blockloop counters, local computations
Functionaccumulators, results being built
Objectinstance fields
Processmodule-level singletons, caches
Persistentdatabase rows, files, key-value stores

Confusing these levels — for example, storing per-request state on a process-level singleton — is a frequent source of multi-tenant bugs.

Mutability

Default to immutable

Prefer const, final, readonly, val, or the language's strongest immutability primitive. Allow mutation only when it materially simplifies the code.

Benefits:

  • The type system tells the reader what cannot change.
  • Concurrency reasoning collapses: an immutable value is safe to share.
  • Time-travel debugging and equality become trivial.

Prefer functional updates over in-place mutation

// In-place
items.push(newItem);

// Functional
const updated = [...items, newItem];

In-place mutation is the right choice when the data structure is large and performance-sensitive, or when the mutation is local and never escapes. Reach for the functional form by default.

Be explicit about ownership

When passing a mutable collection between modules, the calling and called code must agree on who is allowed to modify it. Document this. If the agreement is fragile, return an immutable view (Object.freeze, Collections.unmodifiableList, persistent data structures).

Numbers

Replace magic numbers with named constants

// Bad
if (retries > 3) throw new Error('too many');

// Good
const MAX_RETRIES = 3;
if (retries > MAX_RETRIES) throw new Error('too many');

The name documents the intent and centralizes the value. The exceptions are the few constants that need no explanation: 0, 1, -1, sometimes 2.

Floating-point comparisons

Never compare floats with ===. Use a tolerance appropriate to the scale of the values, or a library function like Math.isClose.

Beware integer overflow in arithmetic

The classic example is binary search:

const mid = (low + high) / 2;        // can overflow
const mid = low + (high - low) / 2;  // safe

In typed languages, prefer the widest sensible numeric type for accumulators.

Booleans

Name intermediate booleans

Compound conditions are easier to read when their parts are named:

// Hard to scan
if (date < SUMMER_START || date > SUMMER_END || isHoliday(date)) { ... }

// Self-documenting
const isOffSeason = date < SUMMER_START || date > SUMMER_END;
const isClosed   = isOffSeason || isHoliday(date);
if (isClosed) { ... }

Avoid double negatives

if (!isNotReady) requires two passes to parse. Pick the affirmative form (isReady) and stick with it.

Strings

  • Distinguish user-facing text (subject to internationalization) from internal identifiers (stable strings used in code paths).
  • Never construct SQL, shell commands, HTML, or other interpreted languages by string concatenation. Use parameterized APIs or established escaping libraries — the alternative is an injection vulnerability.
  • For complex string assembly, prefer template literals or a builder over + chains.

Collections

Choose the structure that matches the semantics

If you need…Use
Unique membershipSet
Key → value lookupMap
Ordered, indexableArray / List
FIFO or LIFOQueue / Deque
Sorted by keyTreeMap / SortedSet

Reaching for a List and writing manual deduplication or linear search is a sign of the wrong choice.

Return read-only views when crossing boundaries

When a method returns a collection that the caller should not modify, return a read-only view. Internal mutation by external callers is a frequent source of "spooky action at a distance."

Pre-Commit Checklist

  • Is every variable initialized at declaration?
  • Is every variable declared close to its first use?
  • Does each variable hold one consistent meaning throughout its life?
  • Is the scope as narrow as the code allows?
  • Are mutable bindings the exception, not the default?
  • Have magic numbers been replaced with named constants?
  • Does each collection match the operations performed on it?

On this page