This TypeScript error occurs when you try to assign a function with a 'void' return type to a function type that expects 'undefined'. While both represent 'no useful return', TypeScript treats them differently: 'void' means 'ignores any return value', while 'undefined' means 'must explicitly return undefined'. The fix involves adjusting function signatures or using type assertions.
The error "Function type '() => void' is not assignable to function type '() => undefined'" highlights a subtle but important distinction in TypeScript's type system between 'void' and 'undefined' return types. In JavaScript, a function without a return statement implicitly returns 'undefined'. However, TypeScript distinguishes between: - **'void'**: Means "this function doesn't return anything useful" - any return value is ignored - **'undefined'**: Means "this function returns the specific value 'undefined'" This distinction exists because TypeScript's 'void' has special behavior: when a function type is declared to return 'void', you can actually assign functions that return any value, but that value will be treated as 'void' at the call site. This enables patterns like 'Array.prototype.forEach' which expects a callback returning 'void' but can accept functions that return values. The error occurs when TypeScript's strict type checking prevents assigning a 'void' function to an 'undefined' function type, as they're not considered equivalent despite both representing "no useful return value".
If you control the function signature, change it from 'void' to 'undefined':
// BEFORE - causes error
type Handler = () => undefined;
const myHandler: Handler = () => {
console.log("Hello");
// Implicitly returns void, not undefined
};
// AFTER - fix by adding explicit return
type Handler = () => undefined;
const myHandler: Handler = () => {
console.log("Hello");
return undefined; // Explicitly return undefined
};
// OR change the type to void if appropriate
type Handler = () => void;
const myHandler: Handler = () => {
console.log("Hello");
// No return needed for void
};Key difference:
- 'void' functions can have no return statement or 'return;' (no value)
- 'undefined' functions must explicitly 'return undefined'
If you're certain the assignment is safe (function doesn't return anything), use a type assertion:
// Function that doesn't return anything (void)
const logMessage = (msg: string): void => {
console.log(msg);
};
// Type expecting undefined return
type Callback = () => undefined;
// Type assertion tells TypeScript to trust you
const callback: Callback = logMessage as () => undefined;
// Alternative syntax
const callback2: Callback = logMessage as unknown as () => undefined;
// Use with caution - only when you're sure the function
// won't be used in a context that depends on undefined returnWarning: Type assertions bypass TypeScript's type checking. Only use when:
1. You know the function truly doesn't return anything
2. The calling code doesn't depend on receiving 'undefined'
3. You've verified runtime behavior matches the assertion
If you control the type definitions, consider whether 'void' is more appropriate than 'undefined':
// BEFORE - overly restrictive
interface EventEmitter {
on(event: string, handler: () => undefined): void;
}
// AFTER - use void for callbacks that don't need to return values
interface EventEmitter {
on(event: string, handler: () => void): void;
}
// Example usage with void
const emitter: EventEmitter = {
on: (event, handler) => {
// handler can return anything, we ignore it
handler();
}
};
emitter.on("click", () => {
console.log("Clicked!");
// Can return a value (ignored) or nothing
return 42; // Allowed with void
});General guideline:
- Use 'void' for callbacks, event handlers, side effects
- Use 'undefined' only when the return value must be exactly undefined
- Most APIs should use 'void' for better compatibility
When a library incorrectly uses 'undefined' instead of 'void', create a wrapper:
// Library with incorrect type
declare module "bad-library" {
export type BadCallback = () => undefined;
export function register(callback: BadCallback): void;
}
// Your wrapper function
function wrapCallback(callback: () => void): () => undefined {
return () => {
callback();
return undefined; // Explicit return to satisfy undefined type
};
}
// Usage
import { register } from "bad-library";
const myHandler = () => {
console.log("Handling event");
// No return - void function
};
// Wrap to satisfy library's incorrect type
register(wrapCallback(myHandler));
// Alternative: inline wrapper
register(() => {
myHandler();
return undefined;
});This approach maintains type safety while working around library limitations.
For library authors, design flexible APIs that accept both 'void' and 'undefined':
// Method 1: Overloads
function register(callback: () => void): void;
function register(callback: () => undefined): void;
function register(callback: (() => void) | (() => undefined)): void {
// Implementation
const result = callback();
// result is void | undefined
}
// Method 2: Generic with conditional type
type CallbackReturn<T> = T extends void ? void : undefined;
function register<T extends void | undefined>(
callback: () => T
): void {
callback();
}
// Both work
register(() => { console.log("void"); });
register(() => { console.log("undefined"); return undefined; });
// Method 3: Union type (simplest)
type FlexibleCallback = (() => void) | (() => undefined);
function register(callback: FlexibleCallback): void {
callback();
}These patterns make your APIs more flexible and user-friendly.
TypeScript's 'void' has unique behavior that can help avoid this error:
// void functions can return values in certain contexts
type VoidFunc = () => void;
// These are all valid!
const f1: VoidFunc = () => true;
const f2: VoidFunc = () => "hello";
const f3: VoidFunc = () => 42;
const f4: VoidFunc = () => ({ key: "value" });
// When called, the return values are treated as void
const result = f1(); // result has type void
// This enables common patterns
const numbers = [1, 2, 3];
const results = numbers.map(n => n * 2); // returns number[]
numbers.forEach(n => n * 2); // forEach expects (n) => void
// Key insight: If an API expects () => void, you can give it
// a function that returns anything, and it will work.
// The reverse is not true: () => void cannot be assigned to () => undefined.
// Use this knowledge to design better types:
// When in doubt, use 'void' for maximum compatibilityRemember: 'void' is more permissive than 'undefined' in assignment contexts.
### The Type Theory Behind void vs undefined
TypeScript's treatment of 'void' and 'undefined' comes from their different roles in the type system:
undefined is a unit type (a type with exactly one value: 'undefined'). It's a proper value that can be assigned, returned, and checked.
void is a bottom-like type in certain contexts. It represents the absence of a meaningful value. The key insight is that 'void' is assignable from any type in function return position (when the function type is contextual), but values of type 'void' cannot be used.
This duality enables:
- Array.prototype.forEach(callback: (value) => void) to accept callbacks that return values
- Type safety by preventing use of ignored return values
- Clean separation between pure functions (return values) and effectful functions (return void)
### Historical Context and JavaScript Compatibility
The distinction exists because TypeScript must balance:
1. JavaScript reality: Functions without return statements return 'undefined'
2. Type safety: Distinguishing intentional 'undefined' returns from ignored values
3. Practical patterns: Allowing 'forEach' and similar APIs to work with any callback
In early TypeScript versions, 'void' and 'undefined' were more interchangeable. As TypeScript added stricter checks, this distinction became more important.
### Compiler Flags and Strictness
The strictness of void/undefined checking depends on compiler flags:
- strictFunctionTypes: When enabled (default in strict mode), function type checking is contravariant in parameter types, which affects how void/undefined are compared
- strictNullChecks: Required for proper undefined checking
- noImplicitReturns: Can help catch missing 'return undefined' statements
### Real-World Examples and Patterns
React Event Handlers:
// React typically uses void for event handlers
type ClickHandler = (event: React.MouseEvent) => void;
const handleClick: ClickHandler = (event) => {
event.preventDefault();
// No return needed - void
};Node.js Callbacks:
// Node.js style callbacks use void
function readFile(path: string, callback: (err: Error | null, data: Buffer) => void): void {
// Implementation
}Functional Programming:
// void for side effects, undefined for explicit absence
const tap = <T>(fn: (value: T) => void) => (value: T): T => {
fn(value);
return value;
};
// Usage
const result = [1, 2, 3]
.map(x => x * 2)
.reduce(tap(console.log), 0); // tap returns void### When to Use Each
Use 'void' when:
- Function performs side effects
- Return value is ignored/unimportant
- Implementing callbacks, event handlers
- Following common library patterns (React, Node.js)
Use 'undefined' when:
- Function must explicitly return undefined (rare)
- Modeling JavaScript's implicit return behavior
- Working with legacy code that depends on undefined returns
- Creating precise type definitions for JavaScript interop
### Migration Strategy
If you encounter this error in a large codebase:
1. First, use 'void' in type definitions (more permissive)
2. Add explicit 'return undefined' where needed
3. Gradually fix library definitions to use 'void'
4. Use ESLint rules to catch problematic patterns
Remember: 'void' is almost always the better choice for new code due to its flexibility and alignment with common JavaScript patterns.
Function expression requires a return type
Function expression requires a return type
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