TS2451Semantic Error
Since TS 1.5

Fix TS2451: Cannot Redeclare Block-Scoped Variable

Learn why TypeScript throws TS2451 when a let or const name is declared twice in one scope, and how to fix cross-file clashes with moduleDetection.

error TS2451: Cannot redeclare block-scoped variable 'X'

What This Error Means

TS2451 fires when the same name is bound twice by let or const in one scope. Unlike var, block-scoped declarations do not merge — each one claims its identifier for the whole block, so a second declaration has nowhere to go and the compiler flags both of them.

The confusing part is what counts as "one scope". Inside a function or a block the rule is obvious. At the top level of a file it depends on whether TypeScript considers that file a module or a script. A file with no top-level import or export is a script, and every script in the program shares one global scope — along with the globals from lib.dom. That is why TS2451 so often points at two files that look completely unrelated.

// The general shape of the error:
// scripts/seed.ts:1:7  - Cannot redeclare block-scoped variable 'prisma'.
// scripts/reset.ts:1:7 - Cannot redeclare block-scoped variable 'prisma'.
//        ~~~~~~
//        both files are scripts, so both declarations are global

Note that this error is specific to let and const bindings. A duplicated class or type alias produces TS2300 ("Duplicate identifier") instead (duplicated interface declarations merge silently, so they raise nothing), and two var declarations of different types produce TS2403. If you are seeing TS2451, the compiler is talking about block scoping specifically.

Common Causes

1. Two Script Files Declaring the Same Top-Level Constant

Standalone scripts, seed files and spec files often have no imports or exports at all. TypeScript therefore treats each one as a global script, and their top-level constants collide.

// ❌ Broken — scripts/seed.ts
const orderTotal = 100
//    ~~~~~~~~~~ Error: Cannot redeclare block-scoped variable 'orderTotal'.
console.log(orderTotal)
// ❌ Broken — scripts/reset.ts
const orderTotal = 0
//    ~~~~~~~~~~ Error: Cannot redeclare block-scoped variable 'orderTotal'.
console.log(orderTotal)

Neither file is wrong on its own. The fix is to stop them from sharing a scope, which is what moduleDetection: "force" does for the whole project:

// ✅ Fixed — tsconfig.json (TypeScript 4.7+)
{
  "compilerOptions": {
    "moduleDetection": "force"
  }
}

Every file is now a module, so orderTotal belongs to its own file and both scripts compile.

2. Colliding With a DOM Global

The dom library declares a long list of globals — name, status, length, history, closed, origin, top. In a script file your top-level declaration lands in the same scope as those, so the error names a variable you only declared once.

// ❌ Broken
const name = "Order export"
//    ~~~~ Error: Cannot redeclare block-scoped variable 'name'.
const status = "ready"
//    ~~~~~~ Error: Cannot redeclare block-scoped variable 'status'.
console.log(name, status)
// ✅ Fixed — the empty export makes this file a module
export {}
 
const name = "Order export"
const status = "ready"
console.log(name, status)

Renaming to exportName and exportStatus also works and is often clearer, but making the file a module is the fix that scales.

3. const Declarations in Unbraced switch Cases

A switch statement has exactly one block. Every case clause shares it, so declaring the same constant in two branches is a redeclaration even though only one branch ever runs.

// ❌ Broken
type OrderEvent = { type: "created" | "shipped" }
 
function labelFor(event: OrderEvent): string {
  switch (event.type) {
    case "created":
      const label = "New order"
      //    ~~~~~ Error: Cannot redeclare block-scoped variable 'label'.
      return label
    case "shipped":
      const label = "Shipped"
      //    ~~~~~ Error: Cannot redeclare block-scoped variable 'label'.
      return label
  }
}
// ✅ Fixed — braces give each case its own block
type OrderEvent = { type: "created" | "shipped" }
 
function labelFor(event: OrderEvent): string {
  switch (event.type) {
    case "created": {
      const label = "New order"
      return label
    }
    case "shipped": {
      const label = "Shipped"
      return label
    }
  }
}

4. A Duplicate Left Behind by a Merge or a Copy-Paste

The plain case: the same name really is declared twice in one block, usually after a bad merge, or by mixing a const with a var of the same name.

// ❌ Broken
const config = { retries: 3 }
//    ~~~~~~ Error: Cannot redeclare block-scoped variable 'config'.
var config = { retries: 5 }
//  ~~~~~~ Error: Cannot redeclare block-scoped variable 'config'.
console.log(config)
// ✅ Fixed — two distinct names for two distinct values
const defaultConfig = { retries: 3 }
const uploadConfig = { retries: 5 }
console.log(defaultConfig, uploadConfig)

How to Fix It

  1. Check whether both locations are in the same file. The compiler reports TS2451 at every conflicting declaration, so the error list tells you immediately which case you are in. Two paths in the list means a module-detection problem; one path twice means a real duplicate.

  2. For a genuine duplicate, rename or delete one declaration. If the two values are different things, give them different names (defaultConfig / uploadConfig). If they are the same thing, remove the second declaration and assign instead: config = { retries: 5 }.

  3. For cross-file clashes, turn on moduleDetection. This is the best project-wide fix and the reason the option exists:

    [object Object]

    It requires TypeScript 4.7 or newer. Every file in the program becomes a module, which also matches how bundlers and Node's ESM loader already see your code.

  4. On older TypeScript, make the individual file a module. Add a real import if the file needs one, or an empty export {} statement at the top if it does not. Both are enough to move the file's top-level declarations out of the global scope.

  5. Brace your switch cases. Wrapping each case body in { … } gives it its own block, so per-case constants stop colliding. It is worth doing by default, even before you hit the error.

  6. Don't rename around a DOM global and call it done. If const name errors in a file that is supposed to be application code, the real problem is that the file is a script. Fix the module status; the naming freedom comes with it. And never reach for var to silence TS2451 — it brings back function-scoped hoisting and can produce TS2403 instead.

FAQ

What causes TypeScript error TS2451?

TS2451 means one scope contains two let or const bindings for the same name. Block-scoped declarations do not merge the way var does, so the compiler rejects both. The three usual shapes are a literal duplicate in one block, two case clauses of a switch (which share a single block), and two files that TypeScript treats as global scripts rather than modules. Duplicated classes and type aliases raise TS2300 instead, and duplicated interfaces merge without any error, so a TS2451 always points at a variable binding.

How do I fix TS2451 when the two declarations are in different files?

Make the files modules. A file with no top-level import or export is a script, and all scripts in a program share one global scope — so const prisma in seed.ts and const prisma in reset.ts are the same binding declared twice. Setting "moduleDetection": "force" in tsconfig.json (TypeScript 4.7+) fixes every file at once. Per file, adding export {} or a real import does the same job. Excluding a folder of standalone scripts from include also works when those scripts genuinely are separate programs.

Why does TypeScript say I cannot redeclare name when I only declared it once?

Because lib.dom.d.ts already declared it. The DOM library puts name, status, length, history, closed and friends on the global scope, and in a script file your top-level const joins them there. The same thing happens with library-provided globals in other setups — Figma plugin typings and generated Cypress or CodeceptJS step definitions are common sources. The fix is the same as for any cross-scope clash: turn the file into a module, or exclude the generated declaration file that is injecting the global.

Practice This

Browse all TypeScript practice challenges to keep sharpening your type-level skills.

Share this reference

Become a TypeScript Pro

Track your progress through 100+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges