Your TypeScript project is failing to compile because the compiler found two different things with the exact same name.

Here’s what’s actually broken at the system level: The TypeScript compiler, tsc, encountered a symbol (a variable, function, class, interface, etc.) that it had already seen declared in the same scope. It can’t have two separate definitions occupying the same identifier in its internal symbol table, so it throws up its hands and stops.

There are a few common reasons this happens:

1. Redeclaring a Variable in the Same Scope

This is the most straightforward cause. You’ve defined a variable, and then later in the exact same block of code (or globally), you try to define another variable with the same name.

Diagnosis: Look for var, let, or const keywords followed by the same identifier in close proximity within the same function or block. The error message will usually point to the line of the second declaration.

Fix: Rename one of the variables.

// Bad
let count = 0;
// ... some code ...
let count = 10; // TS2300: Duplicate identifier 'count'.

// Good
let count = 0;
// ... some code ...
let itemCount = 10;

Why it works: Each variable now has a unique name, so the compiler can distinguish them and assign them to separate memory locations or symbol table entries.

2. Importing the Same Module Multiple Times with Different Names (and then using the same name for something else)

You might have imported a module, aliased it, and then later imported the same module again, or another module with a conflicting name, or even declared a local variable with the same name as an imported symbol.

Diagnosis: Check your import statements and any local declarations. The error often occurs when you have multiple imports that resolve to the same underlying module or when a local declaration clashes with an imported name.

Fix: Ensure your import aliases are unique, or if you’re importing the same module multiple times, only do it once. If a local variable conflicts with an import, rename the local variable.

// Bad
import { data } from './moduleA';
// ...
import { data as dataFromB } from './moduleB'; // Assume moduleB also exports 'data'
// ...
let data = 5; // TS2300: Duplicate identifier 'data'.

// Good
import { data as dataFromA } from './moduleA';
// ...
import { data as dataFromB } from './moduleB';
// ...
let localValue = 5;

Why it works: By giving each imported entity a distinct name (either its original name or an alias) and ensuring local declarations don’t clash with these imported names, the compiler can correctly map identifiers to their sources.

3. Using Global Declaration Merging Incorrectly

TypeScript allows declaration merging for interface and namespace declarations with the same name. If you try to merge something that isn’t an interface or namespace (like a class or function) with the same name, you’ll get a duplicate identifier error.

Diagnosis: Search for multiple declarations of the same name using interface, namespace, class, or function. Pay attention to where these declarations are made – they need to be in a scope where merging is allowed.

Fix: Ensure you are only declaration-merging interfaces and namespaces. If you intend to define multiple related things, use separate names or structure them within namespaces.

// Bad
interface MyObject {
  prop1: string;
}
// ...
interface MyObject { // This is fine, it merges with the above interface.
  prop2: number;
}

// Bad example causing TS2300
class MyConfig {
  static setting = 'default';
}
// ...
class MyConfig { // TS2300: Duplicate identifier 'MyConfig'.
  static anotherSetting = true;
}

// Good
interface MyObject {
  prop1: string;
}
interface MyObject {
  prop2: number;
}

class MyConfig {
  static setting = 'default';
  static anotherSetting = true;
}

Why it works: TypeScript’s declaration merging is a specific feature for interfaces and namespaces. Attempting to merge other types of declarations (like classes) with the same name is not supported and results in a conflict.

4. Duplicate Type Definitions in node_modules or tsconfig.json Paths

This is common in larger projects or when dealing with monorepos. A type definition might be included from multiple different packages, or your tsconfig.json might be configured to include the same directory multiple times under different aliases, leading to the compiler seeing the same file twice.

Diagnosis:

  • node_modules: Run npm ls <package-name> or yarn why <package-name> to see if a package is installed multiple times at different versions or in unexpected locations.
  • tsconfig.json: Examine the paths and baseUrl settings in your tsconfig.json file. Look for overlapping or redundant entries.
  • Type declarations: Check if you’ve explicitly included type declaration files (.d.ts) that might be provided by multiple packages or are already part of the default compiler options.

Fix:

  • node_modules: Use npm dedupe or yarn dedupe to resolve duplicate package versions. If a specific package is causing issues, you might need to manually adjust your package.json or use resolutions (npm) or resolutions (yarn) to force a single version.
  • tsconfig.json: Correct the paths and baseUrl to avoid overlapping definitions. Ensure each source directory is listed only once.
  • Type declarations: Remove redundant explicit references to type declaration files.
// tsconfig.json - Bad example
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@utils/*": ["utils/*"],
      "@shared/*": ["shared/*"],
      "@utils/*": ["common/utils/*"] // Duplicate path alias for @utils
    }
  }
}

// tsconfig.json - Good example
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@utils/*": ["utils/*", "common/utils/*"], // Combine if they are meant to be the same source
      "@shared/*": ["shared/*"]
    }
  }
}

Why it works: By ensuring each source file or module is processed by the compiler only once, and that there’s a single, unambiguous source for any given identifier, the compiler can build its symbol table correctly.

5. Re-exporting and Local Declarations

Similar to import conflicts, if you re-export something from a module and then declare a local variable with the same name in the same file, you’ll hit this.

Diagnosis: Look for export { ... } or export * from '...' statements alongside local let, const, or var declarations using the same identifier.

Fix: Rename either the local declaration or the re-exported name.

// Bad
import { Button } from './ui';
// ...
export { Button }; // Re-exporting
// ...
let Button = 'CustomButton'; // TS2300: Duplicate identifier 'Button'.

// Good
import { Button as UIButton } from './ui';
// ...
export { UIButton };
// ...
let customButton = 'CustomButton';

Why it works: Each distinct entity (the imported Button re-exported as UIButton and the local customButton) has a unique identifier, preventing ambiguity for the compiler.

6. Incorrect Module Resolution with Ambient Declarations

If you have custom ambient declaration files (.d.ts) and your tsconfig.json or build process is set up in a way that causes the compiler to pick up the same module’s types from multiple sources (e.g., from node_modules and a custom d.ts file, or multiple custom d.ts files), you can get this error.

Diagnosis: Examine your tsconfig.json’s include and exclude patterns, as well as any custom typeRoots or types configurations. Look for files that might be providing declarations for the same modules.

Fix: Adjust your tsconfig.json to ensure that each module’s type definitions are only included once. This might involve removing redundant include paths, adjusting typeRoots, or excluding specific directories that contain duplicate type information.

// tsconfig.json - Bad example
{
  "compilerOptions": {
    "moduleResolution": "node",
    "typeRoots": [
      "./node_modules/@types",
      "./src/types" // If src/types also contains some standard types already in node_modules
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/types/**/*.d.ts" // This might be redundant if node_modules already covers it
  ]
}

// tsconfig.json - Good example (assuming standard types are in node_modules)
{
  "compilerOptions": {
    "moduleResolution": "node",
    "typeRoots": [
      "./node_modules/@types"
    ]
  },
  "include": [
    "src/**/*.ts"
  ]
}

Why it works: By explicitly controlling where the compiler looks for type definitions and removing duplicate sources, you ensure that each module’s identifier is defined only once.

After fixing these, the next error you’ll likely encounter is TS2304: Cannot find name 'X', indicating that you’ve fixed a duplicate but now need to ensure all necessary identifiers are properly declared or imported.

Want structured learning?

Take the full Typescript course →