#4533·esbuild

Function declaration in a switch case is lowered to a TDZ-bound let initializer

Author: ko-successCreated Sep 1, 2026Updated Sep 1, 2026

esbuild changes the initialization timing of a block-level function declaration inside a switch statement.

Version:

0.28.2

Input:

javascript
switch (0) {
  case 0: console.log(g()); // fn
  case 1: function g() { return "fn"; }
}

This reproduces without minification, and esbuild outputs:

javascript
switch (0) {
  case 0:
    console.log(g2()); // ReferenceError: Cannot access 'g2' before initialization
  case 1:
    let g2 = function() {
      return "fn";
    };
    var g = g2;
}

Playground reproduction:

https://esbuild.github.io/try/#YgAwLjI4LjIAe21pbmlmeTogZmFsc2V9AGUAZW50cnkuanMAc3dpdGNoICgwKSB7CiAgY2FzZSAwOiBjb25zb2xlLmxvZyhnKCkpOwogIGNhc2UgMTogZnVuY3Rpb24gZygpIHsgcmV0dXJuICJmbiI7IH0KfQ

Enabling minify still produces a incorrect transformation:

javascript
{console.log(c());let c=function(){return"fn"};var g=c} // ReferenceError: Cannot access 'c' before initialization

Playground reproduction:

https://esbuild.github.io/try/#YgAwLjI4LjIAe21pbmlmeTogdHJ1ZX0AZQBlbnRyeS5qcwBzd2l0Y2ggKDApIHsKICBjYXNlIDA6IGNvbnNvbGUubG9nKGcoKSk7CiAgY2FzZSAxOiBmdW5jdGlvbiBnKCkgeyByZXR1cm4gImZuIjsgfQp9

Expected output:

javascript
{console.log(c()); function c(){return"fn"} }

A function declaration and a let declaration with a function-expression initializer do not have the same initialization timing.

For the original function declaration, the binding is initialized with its function object by BlockDeclarationInstantiation when entering the entire CaseBlock, before any case is selected.

For the transformed declaration, the binding is initialized only when execution reaches that statement. Since case 0 is selected, execution never reaches the initializer in case 1, and the binding remains in the temporal dead zone.

The transform should preserve the function declaration or otherwise initialize the function binding before case selection.

Specification references: