This TypeScript error occurs when a generator function is incorrectly typed, either returning a non-Generator type or having incompatible type parameters. Generator functions in TypeScript must return a Generator<T, TReturn, TNext> type, which represents both an iterator and iterable object.
The "Generator function must return Generator type" error appears when TypeScript detects that a function declared with the `function*` syntax (a generator function) has an incorrect return type annotation. Generator functions are special functions that can be paused and resumed using the `yield` keyword, and they always return a Generator object. In TypeScript, generator functions must be typed to return a `Generator<T, TReturn, TNext>` type, where: - `T` is the type of values yielded by the generator - `TReturn` is the type of value returned when the generator completes (defaults to `any`) - `TNext` is the type of value that can be passed to `next()` (defaults to `unknown`) This error typically occurs when: 1. You explicitly annotate a generator function with the wrong return type 2. Type inference fails due to complex yield/return patterns 3. You're using older TypeScript versions with different generator type rules 4. The generator function signature doesn't match the actual implementation
Generator functions must return a Generator<T, TReturn, TNext> type. The simplest form is Generator<T>:
// WRONG - using Iterable or Iterator
function* countToThree(): Iterable<number> {
yield 1;
yield 2;
yield 3;
}
// CORRECT - using Generator
function* countToThree(): Generator<number> {
yield 1;
yield 2;
yield 3;
}
// With all three type parameters
function* createCounter(): Generator<number, void, number> {
let count = 0;
while (true) {
const reset = yield count++;
if (reset === 0) {
count = 0;
}
}
}The three type parameters are:
1. T - Type of yielded values (number in the example)
2. TReturn - Type returned when generator completes (void if no return statement)
3. TNext - Type passed to next() method (number for reset value)
TypeScript can usually infer generator return types correctly. Omit explicit annotations unless necessary:
// Let TypeScript infer - usually works fine
function* generateIds() {
let id = 0;
while (true) {
yield id++;
}
}
// TypeScript infers: Generator<number, never, unknown>
// Only annotate when inference fails or for documentation
function* parseLines(text: string): Generator<string> {
for (const line of text.split('
')) {
yield line.trim();
}
}Check the inferred type by hovering over the function name in your IDE. If TypeScript infers correctly, you don't need explicit annotations.
Async generator functions (using async function*) must return AsyncGenerator<T, TReturn, TNext>:
// WRONG - using Generator for async function
async function* fetchPages(): Generator<Promise<string>> {
for (let i = 0; i < 5; i++) {
const response = await fetch(`/api/page/${i}`);
yield response.text();
}
}
// CORRECT - using AsyncGenerator
async function* fetchPages(): AsyncGenerator<string> {
for (let i = 0; i < 5; i++) {
const response = await fetch(`/api/page/${i}`);
yield response.text();
}
}
// With explicit return type
async function* timer(seconds: number): AsyncGenerator<string, void, unknown> {
for (let i = 0; i < seconds; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield `Tick ${i + 1}`;
}
}Async generators yield Promises or values, and the AsyncGenerator type handles the asynchronous nature.
Generators with multiple yield types or conditional returns need proper type annotations:
// WRONG - mixed yields without proper union type
function* mixedGenerator(flag: boolean) {
if (flag) {
yield "string";
} else {
yield 42;
}
return true;
}
// CORRECT - explicit union type for yields
function* mixedGenerator(flag: boolean): Generator<string | number, boolean> {
if (flag) {
yield "string";
} else {
yield 42;
}
return true;
}
// Complex example with different next() input types
function* interactive(): Generator<string, void, string | number> {
let input: string | number;
while ((input = yield "Enter something:") !== "quit") {
console.log(`You entered: ${input}`);
}
}For generators that never return (infinite loops), use never as the return type:
function* infiniteNumbers(): Generator<number, never> {
let n = 0;
while (true) {
yield n++;
}
}Ensure your tsconfig.json supports generator syntax and has appropriate lib files:
{
"compilerOptions": {
"target": "es2015", // or higher for generator support
"lib": ["es2015", "dom"], // includes generator types
"downlevelIteration": true // if targeting ES5
}
}For older TypeScript versions, you might need:
- "target": "es6" or higher for native generator support
- "downlevelIteration": true if targeting ES5 with polyfills
- Appropriate lib files (es2015 or es2015.generator)
Check your TypeScript version:
npx tsc --versionConsider updating if you're below TypeScript 2.3, which improved generator type inference.
For reusable generator patterns, create utility types:
// Type for a generator that yields T and returns R
type SimpleGenerator<T, R = void> = Generator<T, R>;
// Type for an infinite generator
type InfiniteGenerator<T> = Generator<T, never>;
// Factory function with proper typing
function createCounter(start = 0): Generator<number> {
return (function* () {
let n = start;
while (true) {
yield n++;
}
})();
}
// Generic generator wrapper
function* mapGenerator<T, U>(
generator: Generator<T>,
mapper: (value: T) => U
): Generator<U> {
for (const value of generator) {
yield mapper(value);
}
}
// Usage
const numbers = createCounter();
const doubled = mapGenerator(numbers, n => n * 2);These patterns help maintain type safety across generator compositions.
### Generator Type Parameters Deep Dive
The Generator<T, TReturn, TNext> interface has three type parameters:
1. `T` (Yield type): Type of values yielded with yield expression
- Can be a union type for multiple yield types
- Use unknown or any for dynamic yields (not recommended)
2. `TReturn` (Return type): Type returned when generator completes
- Defaults to any if omitted
- Use void if generator has no return statement
- Use never for infinite generators
3. `TNext` (Next type): Type of value passed to next(value)
- Defaults to unknown if omitted
- Usually matches T for simple generators
- Can differ for bidirectional communication
### Generator vs Iterable vs Iterator
Understanding the differences:
- Iterator<T>: Object with next(): IteratorResult<T> method
- Iterable<T>: Object with [Symbol.iterator](): Iterator<T> method
- Generator<T, TReturn, TNext>: Both Iterator<T> and Iterable<T>, plus return() and throw() methods
All generators are iterables, but not all iterables are generators.
### TypeScript Version Differences
Generator type checking has evolved:
- TypeScript 2.3+: Improved inference for generator return types
- TypeScript 3.6+: Better support for generator type parameters
- TypeScript 4.0+: Variadic tuple types can affect generator inference
If you encounter this error in older codebases, consider updating TypeScript or adding explicit type annotations.
### AsyncGenerator and Observables
For reactive programming patterns, consider these alternatives:
// Async generators for streams
async function* webSocketStream(url: string): AsyncGenerator<MessageEvent> {
const ws = new WebSocket(url);
try {
while (ws.readyState !== WebSocket.CLOSED) {
yield await new Promise(resolve => {
ws.onmessage = resolve;
});
}
} finally {
ws.close();
}
}
// RxJS Observables (alternative pattern)
import { Observable } from 'rxjs';
function intervalObservable(ms: number): Observable<number> {
return new Observable(subscriber => {
let count = 0;
const id = setInterval(() => {
subscriber.next(count++);
}, ms);
return () => clearInterval(id);
});
}### Debugging Generator Type Errors
Use these techniques to debug complex generator type issues:
1. Check inferred types: Hover over generator function in IDE
2. Use type assertions temporarily: function* g() { ... } as any
3. Simplify complex generators: Break into smaller, typed functions
4. Test with concrete types: Replace generics with concrete types temporarily
### Performance Considerations
Generator objects have overhead:
- Each generator maintains its execution state
- TypeScript's generator downleveling for ES5 adds polyfill code
- For performance-critical code, consider alternatives like arrays or custom iterators
However, generators excel at:
- Lazy evaluation of large/infinite sequences
- Complex control flow with pause/resume
- Async data streams with backpressure
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