Question
I am reading about function overloading in the TypeScript language specification, and the examples suggest that a function can have multiple call signatures. However, when I try to write overloads like this, the compiler reports a duplicate identifier error, even though the parameter types are different.
export class LayerFactory {
constructor(public styleFactory: Symbology.StyleFactory) {
// Init...
}
createFeatureLayer(userContext: Model.UserContext, mapWrapperObj: MapWrapperBase): any {
throw new Error('not implemented');
}
createFeatureLayer(layerName: string, style: any): any {
throw new Error('not implemented');
}
}
I also still get the same error if I change the second method to have a different number of parameters. How is function overloading supposed to work in TypeScript, and how should this code be written correctly?
Short Answer
By the end of this page, you will understand how TypeScript function overloading actually works, why writing multiple method bodies causes a duplicate identifier error, and how to define overload signatures with a single implementation. You will also see practical examples, common mistakes, and patterns used in real TypeScript codebases.
Concept
TypeScript supports function overloading, but not in the same way some other languages do.
In TypeScript, overloading means:
- you declare multiple function signatures for callers
- but you provide exactly one implementation
That means this is allowed in principle:
function example(x: string): string;
function example(x: number): number;
function example(x: string | number): string | number {
return x;
}
The first two lines are overload signatures. They describe valid ways to call the function.
The last line is the implementation signature. This is the only place where the actual code exists.
Your original code defines two separate method implementations with the same name inside the class. JavaScript, which TypeScript compiles to, does not support multiple method bodies with the same name. So TypeScript reports a duplicate identifier error.
Why this matters:
- It gives better type safety for callers.
- It lets one function support different input shapes.
Mental Model
Think of function overloading like a customer service desk with multiple signs:
- Sign 1: "Questions about billing"
- Sign 2: "Questions about shipping"
- Sign 3: "Questions about returns"
To the customer, it looks like there are different entry points.
But behind the desk, there is still one employee handling all requests.
In TypeScript:
- the overload signatures are the signs shown to users of the function
- the implementation is the one employee doing the actual work
If you try to create multiple employees with the exact same desk name in the same place, TypeScript complains. That is what happened in your class: you wrote multiple method bodies instead of multiple signatures plus one shared implementation.
Syntax and Examples
The correct syntax for overloading in TypeScript is:
function fn(a: string): string;
function fn(a: number): number;
function fn(a: string | number): string | number {
return a;
}
Class method overloads
For a class method, write the overload signatures first, then a single implementation:
export class LayerFactory {
constructor(public styleFactory: Symbology.StyleFactory) {
// Init...
}
createFeatureLayer(userContext: Model.UserContext, mapWrapperObj: MapWrapperBase): any;
(: , : ): ;
(
: . | ,
: |
): {
( arg1 === ) {
layerName = arg1;
style = arg2;
();
} {
userContext = arg1;
mapWrapperObj = arg2 ;
();
}
}
}
Step by Step Execution
Consider this example:
class Formatter {
format(value: string): string;
format(value: number): string;
format(value: string | number): string {
if (typeof value === 'number') {
return value.toFixed(2);
}
return value.trim();
}
}
const formatter = new Formatter();
const result = formatter.format(42.678);
Step by step:
-
TypeScript sees the call
formatter.format(42.678). -
It checks the overload signatures:
format(value: string): stringformat(value: number): string
Real World Use Cases
Function overloads are useful when one function supports a few clearly different calling patterns.
Common real uses
- Parsing input
- Accept a string, number, or options object.
- API wrappers
- Accept either an ID or a full configuration object.
- UI helpers
- Create a component from different parameter combinations.
- Data formatting
- Format numbers, dates, or strings using one method name.
- Library design
- Expose a friendly API while keeping one implementation internally.
Example: fetch by ID or options
function getUser(id: number): string;
function getUser(options: { email: string }): string;
function getUser(arg: number | { email: string }): string {
if (typeof arg === 'number') {
return ;
}
;
}
Real Codebase Usage
In real projects, developers often combine overloads with runtime checks and simple branching.
Common patterns
Guard clauses
Use early checks to separate cases clearly.
function parse(input: string): string[];
function parse(input: string[]): string[];
function parse(input: string | string[]): string[] {
if (Array.isArray(input)) {
return input;
}
return input.split(',');
}
Validation before processing
Overloads describe valid inputs, but runtime validation is still useful.
function openFile(path: string): void;
function (): ;
(): {
path = arg === ? arg : arg.;
(!path) {
();
}
.();
}
Common Mistakes
1. Writing multiple implementations
This is the mistake from the original question.
Broken code
class Example {
doThing(value: string): void {
console.log(value);
}
doThing(value: number): void {
console.log(value);
}
}
This fails because there are two method bodies.
Correct version
class Example {
doThing(value: string): void;
doThing(value: number): void;
doThing(value: string | number): void {
console.log(value);
}
}
2. Making the implementation too narrow
Broken code
Comparisons
| Approach | Best for | Example | Notes |
|---|---|---|---|
| Function overloads | Different valid call signatures | fn(id: number) and fn(options: object) | Best when callers benefit from distinct signatures |
| Union types | One implementation with flexible input | `fn(value: string | number)` |
| Optional parameters | Missing arguments are acceptable | fn(name: string, age?: number) | Good when later arguments are truly optional |
| Rest parameters | Variable number of arguments | fn(...items: string[]) | Good for lists of values |
Overloads vs union types
Cheat Sheet
// Overload signatures
function fn(a: string): string;
function fn(a: number): number;
// Single implementation
function fn(a: string | number): string | number {
return a;
}
Rules
- Write one or more overload signatures.
- End overload signatures with
;. - Provide exactly one implementation.
- The implementation must handle all overload cases.
- Use runtime checks like
typeof,Array.isArray, or property checks.
Class method pattern
class Example {
method(x: string): string;
(: ): ;
(: | ): | {
x;
}
}
FAQ
Why do I get a duplicate identifier error in TypeScript overloads?
Because you wrote more than one function or method body with the same name. TypeScript overloads require multiple signatures but only one implementation.
How do I overload a method in a TypeScript class?
Write the method signatures first without bodies, then add one final method with the actual implementation.
Does TypeScript support runtime function overloading?
No. At runtime, JavaScript only has one real function. TypeScript overloads are for static type checking.
Should I use overloads or union types?
Use overloads when different call patterns need clearer typing. Use unions when one flexible parameter type is enough.
Can overloaded functions return different types?
Yes. Different overload signatures can specify different return types, as long as the implementation can handle all cases.
Why must the implementation be more general?
Because it has to accept every valid overload case. If one overload accepts a number, the implementation must also be able to receive a number.
Can I overload arrow functions?
Not directly in the same way as function declarations, but you can type a variable with multiple call signatures and assign one implementation to it.
Mini Project
Description
Build a small TypeScript utility class that can search for a product in two different ways: by numeric ID or by product name. This demonstrates how overloads provide a clean API while still using only one implementation method internally.
Goal
Create a class method with overloads that accepts either a product ID or a product name and returns the matching product.
Requirements
- Create a
ProductStoreclass with a list of sample products. - Add overloaded
findProductmethod signatures fornumberandstringinputs. - Implement a single
findProductmethod body that handles both cases. - Return the matching product or
undefinedif nothing is found. - Test the method with both an ID and a name.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.
Angular Dependency Injection: Fix "Can't Resolve All Parameters for Component" Errors
Learn why Angular shows "Can't resolve all parameters for component" and how to fix service injection issues in components.