This TypeScript compiler error occurs when the same identifier (variable, type, interface, or class) is declared multiple times in the same scope. The fix involves removing duplicate declarations, fixing conflicting type definitions, or converting script files to modules.
TypeScript enforces a strict rule that each identifier must be uniquely declared within its scope. When the TypeScript compiler encounters two or more declarations with the same name in the same scope, it emits error TS2300: Duplicate identifier. This error commonly appears in several scenarios: 1. **Accidental duplicate declarations**: You've declared the same interface, type, variable, or function twice in the same file 2. **Conflicting type definitions**: Multiple versions of @types packages or global type declarations conflict 3. **Script vs. module files**: Files without imports/exports are treated as global scripts, causing name collisions across your entire project 4. **Circular dependencies**: Two modules import from each other, creating duplicate type definitions The error is particularly common when working with declaration files (.d.ts), third-party type definitions from @types packages, or when migrating JavaScript code to TypeScript. Global scope pollution is a frequent culprit—any file without at least one import or export statement is considered a global script rather than a module.
First, search your file for the duplicate identifier. Look for multiple declarations:
// WRONG - duplicate interface declaration
interface User {
name: string;
}
interface User {
email: string;
}
// WRONG - duplicate variable
const API_URL = "https://api.example.com";
const API_URL = "https://staging.api.example.com";Remove or merge the duplicates:
// CORRECT - single merged interface
interface User {
name: string;
email: string;
}
// CORRECT - single variable
const API_URL = "https://api.example.com";For interfaces, TypeScript supports declaration merging when declarations are compatible, but it's clearer to have a single declaration.
If your file doesn't have any import or export statements, TypeScript treats it as a global script. This causes all declarations to pollute the global namespace and conflict with other files.
Add an export to convert it to a module:
// BEFORE - global script (problematic)
interface Result {
success: boolean;
}
const calculate = () => {
// ...
};
// AFTER - module (isolated scope)
interface Result {
success: boolean;
}
const calculate = () => {
// ...
};
// Add this line to make it a module
export {};Now your declarations won't conflict with other files. The export {} is an empty export that signals to TypeScript that this is a module file.
Check for duplicate or conflicting @types packages:
# List all @types packages
npm ls @types
# Look for duplicates or multiple versions
npm ls @types/nodeCommon fixes:
# Update to latest version
npm install --save-dev @types/node@latest
# Remove and reinstall all dependencies
rm -rf node_modules package-lock.json
npm install
# Deduplicate packages
npm dedupeAfter cleaning up, restart your TypeScript server in VS Code:
- Press Ctrl+Shift+P (or Cmd+Shift+P on Mac)
- Type "TypeScript: Restart TS Server"
- Press Enter
If the duplicates are coming from third-party type definitions that you can't control, enable skipLibCheck in your tsconfig.json:
{
"compilerOptions": {
"skipLibCheck": true
}
}This tells TypeScript to skip type-checking for all declaration files (.d.ts), which can resolve conflicts from external packages. However, this is a workaround—it's better to fix the root cause if possible.
Note: This option improves compilation speed and works around third-party type issues, but you'll lose type-checking for those libraries.
If two modules export the same name and you need both, use import aliasing:
// BEFORE - conflict if both export 'Result'
import { Result } from "./api";
import { Result } from "./database"; // Error: Duplicate identifier 'Result'
// AFTER - use aliases
import { Result as ApiResult } from "./api";
import { Result as DbResult } from "./database";
// Now use them distinctly
const apiData: ApiResult = { success: true };
const dbData: DbResult = { rows: [] };This pattern is especially useful when different libraries export types with common names like Config, Options, or Response.
If specific declaration files are causing conflicts, exclude them from compilation:
{
"compilerOptions": {
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": [
"node_modules",
"**/*.spec.ts",
"legacy-typings/**/*"
]
}Common exclusion patterns:
- Legacy typings folders
- Duplicate declaration files
- Test files that declare global test types
- Build output directories
Use the tsc --traceResolution command to see which files TypeScript is including and identify the source of duplicates:
npx tsc --traceResolution | grep "duplicate-identifier-name"### Declaration Merging
TypeScript allows certain types of declarations to be merged. Interfaces can be merged if they're compatible:
// These will merge successfully
interface Window {
customProperty: string;
}
interface Window {
anotherProperty: number;
}
// Result: Window has both customProperty and anotherPropertyHowever, merging fails when declarations conflict:
// ERROR - conflicting property types
interface Config {
timeout: string;
}
interface Config {
timeout: number; // Error: Duplicate identifier with different type
}### Module vs. Script Files
Understanding the difference is crucial:
- Module: Any file with import or export statements. Has its own scope.
- Script: Files without imports/exports. Declarations are global.
// script-file.ts (DANGEROUS - global scope)
const sharedValue = 100;
// another-script.ts (DANGEROUS - same global scope)
const sharedValue = 200; // Error: Duplicate identifierAlways prefer modules. Even if you have nothing to export, add export {}.
### Namespaces and Modules
Legacy TypeScript code may use namespaces. These can cause duplicate identifier errors when mixed with modern modules:
// Legacy namespace approach
namespace MyApp {
export interface User {
name: string;
}
}
// Modern module approach
export interface User {
name: string;
}
// If both exist, you get duplicate identifier errorsMigrate to ES modules instead of namespaces for new code.
### Circular Dependencies
Circular imports can cause duplicate identifier issues:
// a.ts
import { TypeB } from "./b";
export interface TypeA {
b: TypeB;
}
// b.ts
import { TypeA } from "./a";
export interface TypeB {
a: TypeA; // Circular reference
}Fix by extracting shared types to a separate file:
// types.ts
export interface TypeA {
b: TypeB;
}
export interface TypeB {
a: TypeA;
}
// a.ts
import type { TypeA, TypeB } from "./types";
// b.ts
import type { TypeA, TypeB } from "./types";### Global Type Augmentation
When augmenting global types (like adding properties to Window), declare them in a .d.ts file:
// global.d.ts
declare global {
interface Window {
myCustomAPI: {
version: string;
};
}
}
export {}; // Makes this a moduleWithout the export {}, the file is treated as a script, and multiple augmentation files will conflict.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript