TS2300Semantic Error
Since TS 1.0

Fix TS2300: Duplicate Identifier

Learn why TypeScript throws TS2300 when two declarations claim the same name, and how to fix duplicate members, type aliases and duplicate @types copies.

error TS2300: Duplicate identifier 'X'

What This Error Means

TS2300 fires when two declarations introduce the same name into the same declaration space and TypeScript cannot merge them. It is a binder error, not a type-checking error: the compiler hits it while building the symbol table, before it ever compares types. That is why no compiler flag turns it off — strict, noImplicitAny and friends are irrelevant here.

The key word is merge. TypeScript deliberately allows some declarations to combine: two interface declarations with the same name become one interface, and namespace, enum and interface declarations happily merge with each other and with functions and classes. Everything else claims its name exclusively. A type alias, a class member, a function parameter, an enum member — declare one of those twice and both declarations are flagged.

// The general shape of the error:
// src/billing/coupon.ts:1:6  - Duplicate identifier 'Coupon'.
// src/billing/coupon.ts:2:11 - Duplicate identifier 'Coupon'.
//                 ~~~~~~
//                 both declarations are reported, not just the second one

The error names the identifier but never explains why the two declarations refuse to merge. Nine times out of ten the answer is one of the four causes below.

Common Causes

1. The Same Member Declared Twice in a Class or Interface

The most mundane cause, and the one most likely to reach a code review: a copy-paste or a badly resolved merge conflict leaves a field listed twice.

// ❌ Broken
class UserAccount {
  email = ''
  displayName = ''
  email = ''
  // ~~~~~ Error: Duplicate identifier 'email'.
}
// ✅ Fixed — keep one declaration per member
class UserAccount {
  email = ''
  displayName = ''
}

The same rule applies inside a single interface body: two properties with the same name are a duplicate identifier. Note the distinction — if the property is declared once in each of two merged interfaces with incompatible types, you get TS2717 ("Subsequent property declarations must have the same type") instead.

2. A Type Alias Colliding With an Interface

Aliases never merge. Declaring type Coupon alongside interface Coupon — a very common outcome when two people model the same domain object in the same file — flags both lines.

// ❌ Broken
type Coupon = { code: string }
//   ~~~~~~ Error: Duplicate identifier 'Coupon'.
interface Coupon { percentOff: number }
//        ~~~~~~ Error: Duplicate identifier 'Coupon'.

Two type aliases with the same name behave identically. Two interface declarations, by contrast, merge silently into one — which is exactly why this asymmetry surprises people.

// ✅ Fixed — one declaration that carries both members
interface Coupon {
  code: string
  percentOff: number
}
 
// Or keep them separate and compose with an intersection:
type CouponBase = { code: string }
type PercentCoupon = CouponBase & { percentOff: number }

3. Two Copies of the Same Global Type Declarations

When TS2300 points at a file in node_modules, the program is loading the same global declarations twice — @types/node hoisted at two versions, @types/jest and @types/mocha both declaring the test globals, or a hand-written shim that redeclares something the lib files already own.

node_modules/@types/node/index.d.ts(78,11): error TS2300: Duplicate identifier 'IteratorResult'.
node_modules/typescript/lib/lib.es2015.iterable.d.ts(39,6): error TS2300: Duplicate identifier 'IteratorResult'.

The same collision is easy to reproduce in your own source. A global declaration of a name the standard library already defines is a duplicate, because a .ts file with no top-level import or export contributes to the global scope:

// ❌ Broken — src/types/legacy-iterators.ts, a compat shim copied from an old @types/node
interface IteratorResult<T> {
  //      ~~~~~~~~~~~~~~ Error: Duplicate identifier 'IteratorResult'.
  done: boolean
  value: T
}

The fix is to remove the duplicate rather than to silence it — delete the shim, and for node_modules conflicts resolve the package graph:

# ✅ Fixed — find both copies, then keep exactly one
npm ls @types/node                # shows every version in the tree
npm i -D @types/node@latest       # or pin one with "overrides" / "resolutions"
npm uninstall @types/mocha        # drop the test-runner typings you don't use
// ✅ Fixed — and stop loading global packages you never asked for
{
  "compilerOptions": {
    "types": ["node", "jest"]
  }
}

4. Duplicate Parameter or Enum Member Names

Find-and-replace across a signature, or an enum that grew one member too many, produces the same error in two places people rarely look.

// ❌ Broken
function applyDiscount(total: number, total: number) {
  //                   ~~~~~            ~~~~~ Error: Duplicate identifier 'total'.
  return total
}
 
enum Role { Admin, Editor, Admin }
//          ~~~~~          ~~~~~ Error: Duplicate identifier 'Admin'.
// ✅ Fixed — every parameter and every enum member gets its own name
function applyDiscount(total: number, percent: number) {
  return total - (total * percent) / 100
}
 
enum Role {
  Admin,
  Editor,
  Viewer,
}

How to Fix It

  1. Read every location the compiler prints. TS2300 is reported at all conflicting declarations, so the output already tells you which two files and lines are fighting. The one you did not expect to see is usually the one to change.

  2. In your own code, delete or rename the second declaration. If both declarations are genuinely needed, merge them: turn a type alias plus an interface into a single interface, or keep two names and compose them with an intersection (type PercentCoupon = CouponBase & { percentOff: number }). Renaming is cheap and always correct — duplicate identifiers are never an intentional design.

  3. For node_modules conflicts, fix the package graph, not the code. Run npm ls @types/<package> (or pnpm why) to find the duplicate versions, dedupe or pin one with overrides/resolutions, and uninstall @types packages you no longer use. Two test-runner typings in one project — @types/jest next to @types/mocha — is the single most common source of "Duplicate identifier 'describe'".

  4. Narrow what the program loads. An explicit "types": ["node", "jest"] in tsconfig.json stops TypeScript from auto-including every package under node_modules/@types, which both fixes the current clash and prevents the next one. Likewise, pick one environment for lib rather than listing "dom" and "webworker" together.

  5. Don't reach for skipLibCheck as the fix. It suppresses errors inside .d.ts files, so a duplicate in node_modules disappears from the output while the two conflicting declarations are still both in your program — and the same duplicate written in a .ts file still fails the build. Treat it as a stopgap that buys you a green build for an afternoon, then resolve the versions properly.

FAQ

What causes TypeScript error TS2300?

TS2300 means two declarations introduce the same name into the same declaration space and TypeScript's merging rules do not allow them to combine. Duplicated class members, two type aliases, a type alias next to an interface of the same name, repeated parameter names and repeated enum members all qualify.

It is raised by the binder, so no compiler flag switches it on or off. What is configurable is which files end up in the program: types, typeRoots, lib, include and your lockfile all decide whether two copies of the same global declarations are loaded at once.

How do I fix Duplicate identifier errors coming from node_modules/@types?

Start by finding the second copy:

[object Object]

If two versions show up, dedupe or pin one (npm i -D @types/node@latest, or an overrides entry in package.json). If two different packages declare the same globals — the classic being @types/jest and @types/mocha both declaring describe and it — uninstall the one you don't use. Then add "types": ["node", "jest"] to tsconfig.json so the compiler only loads the global packages you named, instead of everything under node_modules/@types.

Why do a type alias and an interface with the same name give TS2300 when two interfaces don't?

Interfaces, namespaces and enums are declaration-mergeable by design: two interface Coupon declarations become one interface with the union of their members, which is how the DOM and Node typings extend each other across files. Type aliases have no such rule — an alias is a single, complete definition of a name, so it claims that name exclusively.

interface Coupon { code: string }
interface Coupon { percentOff: number }   // fine — merges into one interface
 
type Discount = { code: string }
type Discount = { percentOff: number }    // TS2300 on both lines

If you want the mergeable behaviour (for module augmentation, for example), use interface. If you want a closed definition that cannot be extended elsewhere, type is the right tool — just make sure the name is unique in its scope.

Related Errors

Practice This

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

Related Concepts

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