TypeScript error

TS2366: A function can reach its end without returning

  • Error TS2366
  • Reported with strictNullChecks
  • Reported with strict
  • Tested with TypeScript 5.9

error TS2366: Function lacks ending return statement and return type does not include 'undefined'. is reported when a function's declared return type (from @returns) does not include undefined and the checker can find a way to reach the closing brace without a return. Falling off the end returns undefined in JavaScript, so the declared type would be wrong on that path.

The surprising case is a chain of if statements that covers every value of a union: the checker does not treat an if chain as exhaustive, so it still sees a path past the last if. A switch over the same cases is treated as exhaustive.

At a glance

CodeTS2366
MessageFunction lacks ending return statement and return type does not include 'undefined'.
TypeScript's categoryerror
Listed underTypes do not match
Turned on by strictNullChecks, strict
In .js files Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file

The message is TypeScript's own text from its diagnostic messages file (Apache-2.0).

What it looks like

shipping.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: shipping.js(2,58): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. shipping.js(2,58): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. shipping.js(2,58): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. checked 21 October 2026
/** @param {"small" | "medium" | "large"} size @returns {number} */error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
export function shippingFor(size) {
  if (size === "small") return 4;
  if (size === "medium") return 6;
  if (size === "large") return 9;
}
Every branch returns, but the checker cannot see that the ifs cover every case.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

What causes it

  • A branch was forgotten: an else, a default value, a throw for impossible input.
  • Every case is handled by if statements, but the checker does not count an if chain as covering the union, so the end of the function is still reachable.
  • strictNullChecks is on (it is part of strict); without it undefined fits every type and this error is not reported.

How to fix it

Use a switch, or make the last case the final return

A switch whose cases cover the whole union lets the checker see that the end is unreachable. In an if chain, drop the last condition and return unconditionally, or end with throw new Error(...) for values that should never arrive.

shipping.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/**
 * @param {"small" | "medium" | "large"} size
 * @returns {number}
 */
export function shippingFor(size) {
  switch (size) {
    case "small":
      return 4;
    case "medium":
      return 6;
    case "large":
      return 9;
  }
}

/**
 * @param {"small" | "medium" | "large"} size
 * @returns {number}
 */
export function shippingForBox(size) {
  if (size === "small") return 4;
  if (size === "medium") return 6;
  return 9;
}
An exhaustive switch, and a final return.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Or include undefined in the return type

If falling through is intended, put it in the type: @returns {number | undefined}. Callers then have to handle the missing value.

TS2366 and noImplicitReturns

TS2366 is about the declared return type. The separate option noImplicitReturns reports a function where some paths return a value and others do not, even without a @returns tag (TS7030).

Gotchas

  • In a .js file the error is reported on the type in the @returns tag, not on the function name.
  • If no path returns anything at all, the message is TS2355 instead.

Sources

Last reviewed 20 October 2026.