DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Temporal Dead Zone and Hoisting: JavaScript Gotchas That Bite in Production

Temporal Dead Zone and Hoisting: JavaScript Gotchas That Bite in Production

Some JavaScript behaviors seem counterintuitive until you understand the mechanics. These are the ones that cause real bugs.

Hoisting: What Actually Happens

JavaScript moves declarations to the top of their scope during compilation — but not initializations:

console.log(x); // undefined (not ReferenceError!)
var x = 5;
console.log(x); // 5

// What JS actually does:
var x; // declaration hoisted
console.log(x); // undefined
x = 5; // initialization stays
console.log(x); // 5
Enter fullscreen mode Exit fullscreen mode

Function declarations are fully hoisted (both declaration and body):

greet(); // 'Hello!' — works before declaration
function greet() { console.log('Hello!'); }
Enter fullscreen mode Exit fullscreen mode

The Temporal Dead Zone (TDZ)

let and const ARE hoisted — but accessing them before initialization throws:

console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 5;

// The TDZ is the time between scope entry and the let/const declaration
Enter fullscreen mode Exit fullscreen mode

This is actually a safety feature — it prevents the confusing undefined behavior of var.

Closure Gotcha: Loop Variables

// Classic bug with var in loops
const fns = [];
for (var i = 0; i < 3; i++) {
  fns.push(() => console.log(i));
}
fns.forEach(fn => fn()); // 3, 3, 3 — not 0, 1, 2!

// Fix with let (block-scoped, new binding per iteration)
for (let i = 0; i < 3; i++) {
  fns.push(() => console.log(i));
}
fns.forEach(fn => fn()); // 0, 1, 2 ✓
Enter fullscreen mode Exit fullscreen mode

this Binding Surprises

class Timer {
  count = 0;

  start() {
    // Bad: this is undefined in strict mode callbacks
    setInterval(function() {
      this.count++; // TypeError: Cannot set property of undefined
    }, 1000);

    // Good: arrow function captures this from outer scope
    setInterval(() => {
      this.count++; // Works correctly
    }, 1000);
  }
}
Enter fullscreen mode Exit fullscreen mode

Floating Point

0.1 + 0.2 === 0.3 // false!
0.1 + 0.2 // 0.30000000000000004

// For money: always use integers (cents) or a library
// Store $9.99 as 999 cents, display as (999 / 100).toFixed(2)
Enter fullscreen mode Exit fullscreen mode

Async/Await Error Swallowing

// Dangerous: errors silently disappear
async function riskyOperation() {
  const result = await fetch('/api/data'); // What if this throws?
  return result.json();
}

// Always handle at the call site
try {
  const data = await riskyOperation();
} catch (err) {
  console.error('Failed:', err);
}
Enter fullscreen mode Exit fullscreen mode

Understanding these mechanics prevents entire classes of production bugs. TypeScript catches many of them at compile time — another reason the AI SaaS Starter Kit ships with strict TypeScript from day one.

Top comments (0)