---
title: 'Naming and Scope Rules'
source: 'https://academia.sh/en/courses/javascript-fundamentals/naming-and-scope-rules'
course: 'JavaScript Fundamentals'
language: en
updated: '2026-08-17T18:09:47+00:00'
license: 'CC BY-SA 4.0'
---

# Naming and Scope Rules

Naming rules and reserved words, the resolution of lexical scope, shadowing, hoisting, and the temporal dead zone.

The previous lesson determined where a name is seen. This lesson answers two more
questions: if the same name is declared in more than one scope, which one applies, and
what happens if a name is read before its declaration line?

Both answers come from a single principle: JavaScript uses **lexical scope**. Which
binding a name corresponds to is determined by looking at where the code is written; it
does not matter who calls whom at runtime. This principle, defined in the Programming
Foundations course's Scope and Lifetime lesson, is the source of every behavior below.

## Naming Rules

A name starts with a letter, an underscore, or a dollar sign; digits may follow after
that. Names are case-sensitive: `threshold` and `Threshold` are two different names.

```js
const measurementCount = 4;
const MEASUREMENT_THRESHOLD = 20;
const $name = "A1";
const _temp = true;
console.log(measurementCount, MEASUREMENT_THRESHOLD, $name, _temp);
console.log("threshold" === "Threshold");
```

```
4 20 A1 true
false
```

The grammar also accepts letters outside the Latin alphabet, but every name in this
course is written with ASCII characters. The reason is practical: characters that look
visually similar but correspond to different code points produce errors that are hard
to tell apart. The code point concept defined in the How Computers Work course's
Character Encodings lesson applies directly here: two names are the same only if their
sequences of code points are the same.

Words reserved by the language cannot be used as names:

```js
const class = 1;
```

```
SyntaxError: Unexpected token 'class'
```

This too is a parsing error; the file does not run at all. The list of reserved words
is written in the standard and includes language constructs like `class`, `const`,
`return`, `typeof`, and `new`.

As a naming convention, this course writes variable and function names starting with a
lowercase letter and continuing with capitalized words (`measurementCount`); threshold
and setting values that will not be recalculated are written in uppercase
(`MEASUREMENT_THRESHOLD`). The convention is not the language's rule; it is chosen for
consistency.

## Lexical Scope

When a name is read, the scope it is currently in is checked first. If the name is not
there, the **enclosing scope** is checked, then that scope's surroundings; if it is not
found even in the outermost scope, a `ReferenceError` is thrown. This chain is derived
from the code's written structure.

```js
const threshold = 20;

function outerScope() {
  const threshold = 25;
  function innerScope() {
    return threshold;
  }
  return innerScope();
}

function caller() {
  const threshold = 30;
  return outerScope();
}

console.log(outerScope());
console.log(caller());
console.log(threshold);
```

```
25
25
20
```

The second line is decisive. Inside the `caller` function, the name `threshold` is
bound to `30`, and `outerScope` is called from there. The result is still `25`:
`innerScope` looks for the name not in the caller's scope but in the enclosing scope of
**where it is itself written**. The call chain does not change the scope chain.

The alternative model — looking for a name in the caller's scope — is called dynamic
scoping and is found in some shell languages. JavaScript does not use this model. The
distinction has a practical consequence: which names a function can see can be
determined by whoever reads it, purely by looking at the source.

## Shadowing

Declaring the same name in an inner scope as in an outer one is called **shadowing**. In
the example above, the `threshold` inside `outerScope` shadows the outermost
`threshold` name. The outer binding does not disappear; it only becomes unreachable in
that region — the last line printing `20` shows this.

Shadowing is useful when used deliberately: carrying a different value of the same
concept in a narrow region is more readable than inventing a new name. When done by
accident, it becomes hard to explain why the outer value was not used. The rule is
simple: if shadowing is happening, it should be deliberate.

## Hoisting

Declarations are processed at the start of the scope, not on the line where they are
written in the source. This behavior is called **hoisting**. The three declaration
kinds are not hoisted the same way, and this is exactly where the difference shows up.

A name declared with `var` is created at the start of the scope and initialized with
`undefined`. It can be read up until the declaration line; when read, it gives
`undefined`:

```js
console.log(typeof varName);
var varName = 20;
console.log(varName);
```

```
undefined
20
```

A name declared with `let` or `const` is also created at the start of the scope, but it
is **not initialized**. Touching the name in the region between its creation and its
declaration line throws an error:

```js
try {
  console.log(letName);
} catch (error) {
  console.log(error.name + ": " + error.message);
}
let letName = 25;
console.log(letName);
```

```
ReferenceError: Cannot access 'letName' before initialization
25
```

This region is called the **temporal dead zone**. The message needs to be read
carefully: it does not say "name not found," it says "cannot access before
initialization." The name exists in the scope; it just has not been bound to a value
yet. Compared with the previous lesson's `esik is not defined` message — that name had
never been declared at all, this one has been declared but not initialized.

The dead zone, combined with shadowing, produces a result that looks counterintuitive:

```js
const threshold = 20;
function buggy() {
  try {
    console.log(threshold);
    const threshold = 25;
  } catch (error) {
    console.log(error.name + ": " + error.message);
  }
}
buggy();
```

```
ReferenceError: Cannot access 'threshold' before initialization
```

The outer `threshold` name, even though it is bound to `20`, could not be read. The
reason is the definition of lexical scope: because `threshold` is declared inside the
function body, every reference to `threshold` in that body points to the **local**
binding. The local binding has not been initialized yet. There is no way to reach the
outer binding; shadowing also applies before the declaration line.

## Function Declarations

Function declarations are hoisted together with their bodies, not just their names.
This is why they can be called before their declaration line:

```js
console.log(average([21.5, 19.75, 23, 18.25]));

function average(array) {
  let total = 0;
  for (const value of array) total += value;
  return total / array.length;
}
```

```
20.625
```

If a function is assigned to a variable as an expression, what gets hoisted is only the
variable declaration; the function itself comes into being on the assignment line. If it
is assigned with `const`, the dead-zone rule applies:

```js
try {
  console.log(average([1, 2]));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
const average = (array) => array.reduce((total, value) => total + value, 0) / array.length;
console.log(average([1, 2]));
```

```
ReferenceError: Cannot access 'average' before initialization
1.5
```

The behavioral difference between the two definition forms will be covered in detail in
the Function Declaration and Expression lesson; here they were compared only from the
hoisting angle.

## Applying It to the Measurement Script

Scope rules directly determine the measurement script's structure:

```js
const THRESHOLD = 20;

function thresholdReport(temperatures) {
  const exceedances = [];
  for (const value of temperatures) {
    if (value > THRESHOLD) {
      const difference = value - THRESHOLD;
      exceedances.push(difference);
    }
  }
  return exceedances;
}

console.log(thresholdReport([21.5, 19.75, 23, 18.25]));
console.log(typeof exceedances);
```

```
[ 1.5, 3 ]
undefined
```

`THRESHOLD` is in the outermost scope and is seen from inside the function through the
lexical scope chain. `exceedances` belongs to the function, `difference` belongs to the
`if` block; neither is accessible from outside. Writing the last line with `typeof` was
required — reading it directly would have thrown a `ReferenceError`.

The structure applies three rules together: every name is declared in the narrowest
scope it is needed in, no shadowing is done, declarations come before use. The third is
not required, thanks to hoisting, but it eliminates every dead-zone error entirely.

## Summary

- Names are case-sensitive; reserved words cannot be used as names, and this is caught
  at the parsing stage.
- JavaScript uses lexical scope: a name is searched for in the enclosing scopes of where
  the code is written; the call chain does not affect this search.
- Declaring the same name in an inner scope shadows the outer binding; shadowing also
  applies before the declaration line.
- `var` is initialized with `undefined` at the start of the scope; `let` and `const` are
  not initialized and stay in the temporal dead zone until their declaration line.
- Function declarations are hoisted together with their bodies; function expressions
  assigned to a variable are not.

## Next Step

The structure of the language — how code is run, how names are declared and resolved —
has been established. Next comes the values these names are bound to. The next topic
covers JavaScript's value model: how many primitive types there are, which
representation the number type uses, and why it carries a limit for integers. Its first
lesson introduces the primitive types one by one.
