This ESLint error occurs when the @typescript-eslint/explicit-function-return-type rule is enabled and you define a function expression without explicitly annotating its return type. While TypeScript can infer return types, this linting rule enforces explicit annotations for better code clarity and type safety.
This error is triggered by ESLint, not the TypeScript compiler itself. Specifically, it comes from the `@typescript-eslint/explicit-function-return-type` rule, which enforces explicit return type annotations on functions and function expressions. TypeScript's type inference is generally powerful enough to determine function return types automatically. However, many teams enable this ESLint rule because explicit return types: 1. **Make code more self-documenting**: Readers immediately know what a function returns without inspecting the implementation 2. **Prevent accidental type changes**: If you modify a function's logic and accidentally change the return type, the compiler will catch it 3. **Improve IDE autocomplete**: Explicit types help IDEs provide better suggestions 4. **Ensure consistent codebase style**: All functions follow the same annotation pattern The rule primarily targets function expressions (arrow functions, function expressions assigned to variables) but can be configured to also check function declarations.
The most straightforward fix is to add a return type annotation after the parameter list:
// BEFORE: ESLint error - no return type annotation
const greet = (name: string) => {
return `Hello, ${name}`;
};
// AFTER: Explicit return type added
const greet = (name: string): string => {
return `Hello, ${name}`;
};For function expressions with multiple possible return types:
// Union return type
const getValue = (useDefault: boolean): string | number => {
return useDefault ? "default" : 42;
};
// Void for functions with no return value
const logMessage = (msg: string): void => {
console.log(msg);
};
// Promise return type for async functions
const fetchData = async (id: string): Promise<User> => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};If you define a function type separately, the rule allows you to skip the inline annotation (when allowTypedFunctionExpressions is enabled, which is default):
// Define function type
type GreetFunction = (name: string) => string;
// BEFORE: Error - function expression needs return type
const greet = (name: string) => {
return `Hello, ${name}`;
};
// AFTER: Variable has function type annotation
const greet: GreetFunction = (name) => {
return `Hello, ${name}`;
};This is especially useful for callback patterns:
// React event handler example
type ClickHandler = (event: React.MouseEvent<HTMLButtonElement>) => void;
const handleClick: ClickHandler = (event) => {
event.preventDefault();
console.log("Clicked!");
};If you want to keep type inference for function expressions, update your .eslintrc.js or .eslintrc.json:
{
"rules": {
"@typescript-eslint/explicit-function-return-type": [
"error",
{
"allowExpressions": true,
"allowTypedFunctionExpressions": true,
"allowHigherOrderFunctions": true
}
]
}
}Configuration options:
- allowExpressions: true - Ignores function expressions (arrow functions, function expressions)
- allowTypedFunctionExpressions: true - Allows functions with type annotations on the variable
- allowHigherOrderFunctions: true - Allows functions that return other functions
- allowDirectConstAssertionInArrowFunctions: true - Allows as const assertions
- allowConciseArrowFunctionExpressionsStartingWithVoid: true - Allows () => void expr
This gives you flexibility while maintaining type safety where it matters most.
If your team prefers to rely on TypeScript's type inference, you can disable the rule:
{
"rules": {
"@typescript-eslint/explicit-function-return-type": "off"
}
}Or use inline comments to disable it for specific functions:
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const calculateTotal = (items: Item[]) => {
return items.reduce((sum, item) => sum + item.price, 0);
};Note: Disabling this rule sacrifices some code clarity and type safety benefits. Consider using the configuration options in Step 3 instead for better balance.
Even if you disable the rule for internal functions, consider keeping explicit return types for public APIs and exported functions:
// Public API - explicit return type recommended
export const processUser = (user: User): ProcessedUser => {
return {
id: user.id,
displayName: `${user.firstName} ${user.lastName}`,
isActive: user.status === "active",
};
};
// Internal helper - type inference OK
const formatName = (first: string, last: string) => {
return `${first} ${last}`;
};This can be enforced with the @typescript-eslint/explicit-module-boundary-types rule instead, which only checks exported functions.
### Balancing Type Safety and Developer Experience
Many teams debate whether to enforce explicit return types. The trade-offs:
Pros of explicit return types:
- Acts as inline documentation
- Catches refactoring mistakes early
- Prevents accidental any returns
- Makes code reviews easier
Pros of type inference:
- Less verbose code
- TypeScript's inference is very accurate
- Faster development for simple functions
- Reduces maintenance burden when types change
Recommended approach: Use the explicit-module-boundary-types rule instead, which only requires return types on exported functions. This balances internal flexibility with external type safety.
### Higher-Order Functions
Functions that return functions can be tricky:
// Without explicit types - may trigger error
const createMultiplier = (factor: number) => {
return (value: number) => value * factor;
};
// With explicit types - clear and error-free
const createMultiplier = (factor: number): ((value: number) => number) => {
return (value: number): number => value * factor;
};
// Or use a type alias for clarity
type Multiplier = (value: number) => number;
const createMultiplier = (factor: number): Multiplier => {
return (value: number): number => value * factor;
};### React Component Props Callbacks
In React, callback props benefit from explicit types:
interface ButtonProps {
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
label: string;
}
// Explicit return type matches interface
const Button = ({ onClick, label }: ButtonProps): JSX.Element => {
return <button onClick={onClick}>{label}</button>;
};### Generic Functions
Generic functions should always have explicit return types when the return type involves the generic:
// Explicit return type clarifies generic usage
const firstElement = <T>(arr: T[]): T | undefined => {
return arr[0];
};
// Complex generic - explicit return type essential
const mapValues = <T, U>(
obj: Record<string, T>,
fn: (value: T) => U
): Record<string, U> => {
const result: Record<string, U> = {};
for (const key in obj) {
result[key] = fn(obj[key]);
}
return result;
};### Async Functions and Promises
Always explicitly type async function returns to avoid confusion:
// Implicit - returns Promise<number> but not obvious
const fetchCount = async () => {
const response = await fetch("/api/count");
return response.json();
};
// Explicit - clearly a Promise
const fetchCount = async (): Promise<number> => {
const response = await fetch("/api/count");
return response.json();
};This helps catch errors where you forget to await or incorrectly type the resolved value.
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
Type 'X' is not assignable to inferred conditional type
How to fix "Type not assignable to inferred conditional" in TypeScript