All posts
typescriptcompiler

Fixing "Duplicate identifier" in TypeScript

Why TypeScript reports duplicate identifier errors across files and declarations, and how to fix each common cause.

SR

Suhail Roushan

August 6, 2026

·
4 min read
·
0 views

Duplicate identifier 'X' means TypeScript found two declarations for the same name in a scope where only one is allowed — sometimes an obvious same-file mistake, but often a more confusing cross-file collision involving global type declarations, ambient .d.ts files, or conflicting package type definitions.

This error means TypeScript's compiler encountered the same identifier declared more than once in a context where redeclaration isn't valid — variables, types, interfaces (with some nuance, since interfaces support declaration merging), and imports all have specific rules about what counts as a genuine conflict versus valid re-declaration.

Why This Error Happens

Most simply, this happens from copy-paste duplicating a declaration within the same file. Less obviously, it happens across files when two ambient .d.ts files declare conflicting global types, when two versions of the same package (via different dependencies each pulling in their own copy) ship incompatible type definitions for the same global, or when a variable declared with const/let collides with an import of the same name.

Reproducing the Error

Same-file accidental redeclaration:

const config = loadConfig();
// ... 200 lines later, forgotten the above declaration
const config = loadDefaultConfig();
// Error: Duplicate identifier 'config'.

Conflicting global declarations across two .d.ts files:

// types/global.d.ts
declare global {
  interface Window {
    analytics: AnalyticsClient;
  }
}

// third-party-lib/index.d.ts (from a dependency)
declare global {
  interface Window {
    analytics: DifferentAnalyticsShape; // conflicts with the above
  }
}
// Error: Duplicate identifier 'analytics'.

Core Concepts Behind This Error

interface supports declaration merging by design, so two interface Foo {} declarations with the same name in the same scope don't conflict — they merge into one combined interface; type aliases and class declarations don't support this, so duplicating either of those does produce this error, which is a useful distinction when deciding how to structure shared type definitions.

Multiple versions of the same npm package, each shipping its own type definitions, is a common source of confusing cross-file duplicate identifier errors — particularly for packages that augment global types (like @types/node conflicts, or a library declaring global Window extensions) where two installed versions declare the same global differently.

TypeScript project references or multiple tsconfig.json files including overlapping source files can cause the same file to be type-checked in two different compilation contexts, sometimes manifesting as duplicate identifier errors that don't make sense from looking at a single file in isolation.

An import name colliding with a local declaration of the same name is a straightforward but easy-to-miss cause, especially after refactoring or merging code from another file where the same variable/type name was independently chosen.

Fixing "Duplicate Identifier"

Fix 1: For same-file duplication, remove or rename one of the conflicting declarations:

const config = loadConfig();
const defaultConfig = loadDefaultConfig(); // renamed to avoid collision

Fix 2: For conflicting global augmentations across files, consolidate into a single declaration or ensure they're actually compatible:

// types/global.d.ts — single source of truth for this augmentation
declare global {
  interface Window {
    analytics: AnalyticsClient;
  }
}
// Remove or align the conflicting declaration in the other file

Fix 3: For duplicate type definitions from multiple package versions, deduplicate your dependency tree:

pnpm dedupe
# or investigate with:
pnpm why @types/node

Fix 4: For import/local-name collisions, rename the import using as:

import { config as importedConfig } from "./external-config";
const config = loadConfig(); // no longer collides

Why Do Interfaces Merge While Types Don't?

Interfaces were designed from the start to support declaration merging as a deliberate feature — it enables patterns like extending third-party library types (adding a property to Window, or augmenting an Express Request object) without modifying the original source. type aliases were designed as a more general-purpose construct for unions, intersections, and complex type expressions where merging semantics wouldn't make sense or could be genuinely ambiguous — this is a deliberate design distinction between the two, not an inconsistency.

Preventing This Error in Production

Keep global type augmentations centralized in a small number of well-organized .d.ts files rather than scattered across many files, reducing the chance of accidental conflicting declarations. Run pnpm dedupe (or your package manager's equivalent) periodically to catch multiple-version type definition conflicts before they surface as confusing compiler errors, and prefer interface over type specifically when you anticipate a type might need extension via declaration merging.

If you hit this error, check whether it's a straightforward same-file duplication first (the easy case), then check for global augmentation conflicts across .d.ts files or multiple package versions if the error doesn't make sense from a single file's contents alone.

Related posts

Written by Suhail Roushan — Full-stack developer. More posts on AI, Next.js, and building products at suhailroushan.com/blog.

Get in touch