In TypeScript, when two same-name interface declarations each declare a member called `format`, when is that a compile error and when do you get an overload set?
answer
- method syntax versus property syntax
- overloads on one side, strict equality on the other
- subsequent declarations must agree
- later declarations are tried first
- string-literal parameters bubble up
basics
~20 sMethod-syntax members of the same name become an overload set when interfaces merge. Ordinary properties, including function-typed ones, must be unique or declared with an identical type — otherwise the compiler reports that subsequent property declarations must have the same type.
solid answer
~40 sMerging distinguishes methods from properties. If both declarations write `format(value: string): string` style method syntax, the members are treated as overloads of one function and the merged interface has a signature list. If they are properties — `format: (value: string) => string` — the same-name rule is strict: identical types merge harmlessly, different types are an error saying subsequent property declarations must have the same type. So `count: number` in one and `count: string` in the other fails, while two `count: number` declarations are fine. Order matters for overload resolution: members from the later declaration group are placed earlier in the merged overload list, so the last-loaded augmentation is tried first. That is why one library augmenting another's interface can change which overload wins without changing a single line of the original.
code
typescript · 18 linesinterface Fmt {
render(value: string): string;
}
interface Fmt {
render(value: number): string;
label: string;
}
// interface Fmt { label: number } // error: subsequent property
// declarations must have the same type
const f: Fmt = {
render: (v: string | number) => String(v),
label: 'fmt',
};
console.log(f.render(1), f.render('a'));go deeper
Know that merged interfaces cannot contradict each other: a repeated property must have the same type, and methods with the same name pile up as overloads.
Explain the method-versus-property distinction precisely, quote the must-have-the-same-type rule, and note that identical repeated declarations are legal.
Reason about ordering — later declarations come first in the merged overload list — and diagnose the real-world case where two augmentations collide on a property or silently reorder overloads.
Own the API consequence: declaring a member with method syntax invites outside signatures you cannot review, and shared augmentable interfaces need naming conventions and optionality rules before two dependencies fight over the same name.
## Two member kinds, two rules When the compiler merges same-name interfaces it does not simply concatenate members. It applies one rule to method-shaped members and a stricter rule to everything else. **Method syntax** — `format(value: string): string` — declares a call signature attached to a name. Several declarations of the same method name across merged interfaces are legal and become **overloads** of one function: ```ts interface Fmt { render(value: string): string } interface Fmt { render(value: number): string } // Fmt.render now has two call signatures. ``` **Property syntax** — `format: (value: string) => string`, or any non-method member like `count: number` — declares a property whose type happens to be a function type. Here the rule is: names must be unique across the merged declarations, or, if repeated, must be declared with the *same* type. Repeating a property with a different type is an error stating that subsequent property declarations must have the same type, naming both the expected and the found type. This is the distinction that surprises people. These two members look interchangeable when you write the interface, and for assignability purposes they are close cousins — but for merging they behave completely differently. If you want an interface that stays open to additional signatures, declare the member with method syntax; if you want a member nobody can quietly extend, declare it as a function-typed property. ## Repetition is allowed when the types match Merging does not require the declarations to be disjoint. Declaring `id: string` in both halves is legal and produces one `id: string`. That matters for augmentations: two independent packages may both add the same member, and as long as they agree on the type, the program still compiles. The moment they disagree, somebody has to resolve it — there is no precedence rule for properties, no last-one-wins, and no way to scope one of them away. ## Order of the merged overload list Overloads are ordered, because resolution picks the first signature that matches. In a merged interface, members contributed by the **later** declaration are placed **before** those of earlier declarations in the merged list. So if a package declares an interface and your augmentation adds another `render` signature, yours is considered first. There is one refinement to know: a signature whose parameter type is a single string literal is a *specialized* signature and is hoisted toward the top of its overload list, so it is checked before more general signatures. That is what makes DOM-style APIs work, where a call with a specific literal argument gets a precise return type while the general signature catches everything else. The practical reading: overload order in a merged interface is not fully in the original author's hands. An augmentation loaded later can shadow an existing signature for calls that both would accept. When a call resolves to a surprising signature, the merge order is where to look. ## Why the rules are asymmetric Overloading is meaningful only for callables — you can describe the same operation several ways. A data property has one type; two contradictory declarations of the same property would leave the checker with no consistent answer, so it refuses. Nothing here is a runtime mechanism: merged overloads are erased along with the rest of the type layer, and the overload set only ever influenced which signature the checker used to check a call site. ## Conflict in practice The usual place these rules bite is augmentation. Two libraries in one program both extend a widely-used interface — a request object, a config object, a global — and both choose the obvious member name: ```ts // library A's augmentation interface AppContext { user: string } // library B's augmentation interface AppContext { user: { id: string } } // error: types must match ``` The build fails with no obvious owner: neither package is wrong, and you cannot un-apply one of them, because merging is program-wide. The mitigations are all preventative — namespace your added members (`myFeature: { ... }` rather than a bare common noun), keep additions optional so they describe reality, and prefer a local wrapper type when the extension is only for your own code. A second, quieter failure is the overload one: two packages add signatures for the same method, both match a given call, and the one loaded later silently wins. There is no diagnostic for that — the code compiles and the inferred return type is simply not the one you expected. Reading the merged signature list in an editor tooltip is usually the fastest way to see what actually happened.
- If a merged interface ends up with two overloads that both match a call, which one is used?The first matching signature in the merged list. Members from the later declaration group are placed earlier, so an augmentation loaded after the original is checked before it. Specialized signatures — those taking a single string literal parameter — are hoisted further toward the top. Nothing warns you about this; the call simply resolves to a signature you may not have expected.
- Two teams both need to add a field to the same augmented interface. How do you keep them from colliding?Give each addition an owner-specific name rather than a generic noun, or have each team add a single namespaced member holding its own object. Also keep additions optional, which describes reality more accurately and avoids fights over required-versus-optional. If only one team's code needs the field, a local wrapper or intersection type avoids the shared surface entirely.
- Does declaring a member with method syntax rather than a function-typed property change anything besides merging?Yes — the two differ in how parameter types are checked for assignability, which is a separate topic, and in whether the member participates in overloading. For merging specifically, method syntax is the extensible form and a function-typed property is the closed one, so the choice is effectively a decision about whether outside declarations may add signatures.
saying these in an interview costs you the question
- Thinks any repeated member in merged interfaces is an error
- Says the later declaration's property type overrides the earlier one
- Treats a function-typed property as overloadable
- Assumes merged overloads are tried in source order, earliest first
- Believes overload resolution happens at runtime