A declaration creates a namespace, a type, a value, or a mix (Handbook table: a class makes type and value, an interface only a type, a namespace makes namespace and value). When two declarations with the same name create compatible meanings, TypeScript merges them.
- Interface + interface (the common case): members join into one interface. Function members become overloads, later declarations first. Non-function members with the same name must have the same type, else TS2717.
- Namespace + namespace: exported members are combined. Non-exported members are visible only to the block that declared them, so the second block cannot see them (TS2304).
- Namespace + function / class / enum (the namespace comes after): adds properties to the function, inner types or statics to the class, or helper functions to the enum. This is the only way to type a callable-with-properties built in plain JavaScript.
- Interface + class merges too, which types members the class does not initialise.
- Do not merge:
typealiases, class + class, class + variable (duplicate identifier, TS2300). - Module augmentation (
declare module "./x",declare global) is merging across files: it patches existing declarations only.
Are namespaces still used? Not to organise application code: a file is already a module and the importer names it, so wrapping exports in a namespace is "needless namespacing" (Handbook). They remain for declare namespace in global/UMD typings, for function and enum merging, and in older codebases. A namespace containing values emits an IIFE, so it is runtime code, and erasableSyntaxOnly rejects it (TS1294). Type-only and declare namespaces are still allowed.