var declaration
| Vulnerability potential | Low |
| DDoS potential | None |
var is function-scoped and hoisted; prefer const for immutable bindings or let for mutable ones
Impact
var is function-scoped, not block-scoped, and its declaration is hoisted to the
top of the enclosing function while its assignment stays in place. As a result a
var declared inside an if, for or try block leaks to the whole function,
reading it before the assignment yields undefined instead of a
ReferenceError, and re-declaring the same name silently succeeds. The classic
symptom is the loop-closure bug: every closure created in a for (var i ...)
loop captures the same single i, so they all observe its final value. These
are correctness defects — stale or shared values, accidental shadowing,
variables that appear “defined” before their initialiser — that produce wrong
output rather than a crash, and are easy to miss in review.
Vulnerability potential
var has little direct security relevance: it is a scoping and style defect, not
a memory-safety or injection issue. The only indirect risk is that the
function-scope leak or a closure capturing the wrong value produces incorrect
logic in code that happens to make a security decision — for example a loop that
was meant to validate each item but ends up acting on the last one. That is a
correctness failure that could weaken a check, not a vulnerability introduced by
var itself, so the rating stays Low.
Technical details
During the creation phase of a function’s execution context the engine hoists
every var declaration and binds the name to undefined; the assignment runs
later when control reaches it. let and const are hoisted too but live in the
temporal dead zone until their declaration executes, so reading them early
throws a ReferenceError — the engine turns a silent bug into an error.
Scope granularity
var ignores block boundaries; only functions (and the module/global scope)
create a var scope. let/const are block-scoped, so each { ... }, loop
body and catch clause gets its own binding.
Loop bindings
for (let i = 0; ...) creates a fresh binding of i for every iteration, so
a closure created in the body captures that iteration’s value. for (var i ...)
has one binding shared by all iterations, which is why the closures all see the
final value. This is the single most common var bug.
Redeclaration
Re-declaring with var in the same scope is allowed and silently merges, hiding
copy-paste mistakes; let/const raise a SyntaxError on redeclaration.
Catching the issue
Linters
ESLint’s no-var rule flags every var and is autofixable to let/const;
pair it with prefer-const so bindings that are never reassigned become
const, and with block-scoped-var / no-redeclare if some var must remain.
no-loop-func catches the closure-in-loop pattern specifically.
Type checkers and bundlers
TypeScript reports use-before-declaration for let/const thanks to the TDZ
and will surface the leaked-scope cases once var is removed. Most style guides
(Airbnb, Google, StandardJS) forbid var outright.
Migration note
Mechanically replacing var with let/const is usually safe, but watch for
code that relied on function-scope leakage (a var used after its block);
those spots need the declaration hoisted to the right block manually.
How to reproduce
Observe that all var closures print 3, while let prints 0 1 2, and that a
var is readable (as undefined) before its declaration.
// hoisting: no error, prints undefined
console.log(hoisted); // undefined
var hoisted = 1;
// shared loop binding
const withVar = [];
for (var i = 0; i < 3; i++) withVar.push(() => i);
console.log(withVar.map((f) => f())); // [3, 3, 3]
const withLet = [];
for (let j = 0; j < 3; j++) withLet.push(() => j);
console.log(withLet.map((f) => f())); // [0, 1, 2]