JavaScript Constants with Const Keyword: Understanding Immutable References

JavaScript Constants with Const Keyword: Understanding Immutable References

Master block scope, the Temporal Dead Zone (TDZ), and true object immutability using Object.freeze().

Author
Nguyen Bao Huy
17:20:00 08/02/2026
3 min read
0 comments

1. The Core Mental Model: Reference vs. Value Immutability

The const identifier is frequently misunderstood by developers transitioning to modern ECMAScript. Declaring a variable with const creates an immutable binding in the execution context's lexical environment—it does not create deeply immutable data structures.

Primitive Types: Absolute Immutability

When holding primitive types (number, string, boolean, null, undefined, symbol, bigint), the variable holds the raw value on the call stack. Reassigning the identifier throws an immediate runtime exception:

javascript
const REQUEST_TIMEOUT_MS = 5000;

// Throws TypeError: Assignment to constant variable.
REQUEST_TIMEOUT_MS = 10000;

Reference Types: Mutable Heaps with Pinned Pointers

When binding complex data structures (Object, Array, Map, Set), the const variable holds only the memory pointer (reference address) pointing to the heap:

javascript
const clientConfig = {
  endpoint: "https://api.stackra-agency.com/v1",
  retryCount: 3,
};

// 1. Modifying internal properties is VALID:
clientConfig.retryCount = 5;
clientConfig.environment = "production";

// 2. Rebinding the memory reference is INVALID:
// Throws TypeError: Assignment to constant variable.
clientConfig = { endpoint: "https://backup.stackra-agency.com" };

2. Array Mutability with const

Arrays assigned to const guarantee that the variable will always point to the exact same array buffer in memory. Internal element operations remain fully accessible:

javascript
const workerNodes = ["edge-us-east", "edge-eu-central"];

// Valid: In-place mutations
workerNodes.push("edge-ap-southeast");
workerNodes[0] = "core-primary";

// Valid: Non-mutating methods returning new references assigned to a new variable
const filteredNodes = workerNodes.filter(node => node.startsWith("edge"));

// Invalid: Attempting to assign a new array reference
// Throws TypeError: Assignment to constant variable.
workerNodes = ["gateway-01"];

3. Achieving True Immutability: Object.freeze() vs. Deep Freeze

To prevent property tampering or mutation in production configurations, pair const with Object.freeze():

javascript
// Shallow Freeze
const SYSTEM_DIRECTIVES = Object.freeze({
  SERVICE_NAME: "AUTH_BROKER",
  RETRIES: 3,
  NETWORK: {
    PORT: 8080,
  },
});

// Throws error in strict mode (fails silently in non-strict mode)
SYSTEM_DIRECTIVES.RETRIES = 5; 

// Caveat: Nested objects remain mutable (Shallow freeze)
SYSTEM_DIRECTIVES.NETWORK.PORT = 9000; // Successfully mutated!

Enterprise Deep Freeze Utility

For deeply nested configuration payloads (e.g., microservice envelopes, cloud policies), enforce recursive deep immutability:

javascript
function deepFreeze(object) {
  const propNames = Reflect.ownKeys(object);

  for (const name of propNames) {
    const value = object[name];
    if ((value && typeof value === "object") || typeof value === "function") {
      deepFreeze(value);
    }
  }

  return Object.freeze(object);
}

const IMMUTABLE_PAYLOAD = deepFreeze({
  auth: {
    claims: ["read:telemetry", "write:agents"],
    tokenConfig: { ttlMs: 3600000 },
  },
});

// Throws error: Cannot assign to read only property 'ttlMs'
IMMUTABLE_PAYLOAD.auth.tokenConfig.ttlMs = 7200000;

4. Block Scope & The Temporal Dead Zone (TDZ)

Like let, variables declared with const adhere to block scope ({ ... }) and are not hoisted with a default value of undefined.

The Temporal Dead Zone Mechanics

Accessing a const variable before its syntactic declaration produces a ReferenceError because the identifier resides in the Temporal Dead Zone:

javascript
function evaluateMeshState() {
  // Accessing activeCluster here throws:
  // ReferenceError: Cannot access 'activeCluster' before initialization
  console.log(activeCluster);

  const activeCluster = "cluster-aws-us-west-2";
}

5. Enterprise Best Practices & Linters

At StackRa Agency, code quality pipelines mandate the following conventions across all TypeScript and JavaScript repositories:

  1. Default to const: Assign all variables using const by default. Switch to let only when dynamic reassignment is explicitly required (e.g., loop iterators, mathematical accumulators).
  2. Never Use var: Treat var as an antipattern due to function-scope leakage and hoisting bugs.
  3. Automate via ESLint: Enforce the following rules in CI/CD checks:
    • prefer-const: Flags variables declared with let that are never reassigned.
    • no-var: Blocks legacy var declarations entirely.
    • no-const-assign: Catches runtime reassignment attempts during compile/lint cycles.
1 engineers appreciated this

First Lesson

You are at the beginning of this curriculum.

Latest Tutorial

More chapters coming soon to this topic.

Author avatar

Nguyen Bao Huy

Lead Fullstack & AI Solutions Engineer

Specializing in Next.js App Router, React 19, TypeScript, and modern design systems. Passionate about creating seamless user experiences.

Discussion

0

No comments yet

Be the first to share your thoughts, question a concept, or provide additional tips!

Leave a Reply

Share your insights, questions, or solutions with the developer community.

Your avatar
0 / 500 characters