noImplicitReturns

Errors when some code path through a function falls off the end without returning.

Signature
"noImplicitReturns": boolean  // default: false
  • Added in TypeScript 1.8

Parameters

ParameterTypeDescription
$noImplicitReturnsoptionalboolean= falseCheck all code paths in a function to ensure they return a value.
$noFallthroughCasesInSwitchoptionalboolean= falseReport an error when a non-empty switch case runs into the next one without break, return or throw. Added in TypeScript 1.8.

Return value

booleanAn error on any function where at least one code path returns a value and another falls off the end.

Errors

Error TS7030, "Not all code paths return a value." A separately annotated return type yields TS2366 instead, "Function lacks ending return statement and return type does not include 'undefined'."

Changelog

  • PHP 1.8Introduced alongside noFallthroughCasesInSwitch.
  • PHP 2.0Exhaustive switch statements over a union make the end of the function unreachable, so they no longer trigger the error.

noImplicitReturns rejects a function where one branch returns a value and another quietly falls off the end. That silent path returns undefined, and if the caller expected a string you get a TypeError somewhere far away rather than an error at the source. The diagnostic is TS7030, "Not all code paths return a value."

Its partner

noFallthroughCasesInSwitch covers the neighbouring hazard: a non-empty case that runs into the next one because a break was forgotten. It requires every non-empty case to end in break, return or throw (TS7029). Empty cases stacked for grouping are still fine. Turn both on together — they catch the two halves of the same class of control-flow slip.

What trips people up

A function that returns void, or whose return type includes undefined, is exempt — the flag only fires when returning nothing is inconsistent with returning something.

An exhaustive switch over a union is understood: if every member is covered and each case returns, the end of the function is unreachable and there is no error. The same switch over a plain string does error, because the compiler cannot prove exhaustiveness. Adding a default that throws is the usual fix, and it doubles as a runtime guard.

Example

Example · typescript
type Status = "queued" | "running" | "done";

function label(s: Status): string {
  if (s === "queued") return "waiting";
  if (s === "running") return "in progress";
  return "finished";
}

// TS7030 under noImplicitReturns: the non-"done" path falls off the end.
function bad(s: Status) {
  if (s === "done") {
    return "finished";
  }
}

console.log([label("queued"), label("done"), bad("running")]);
// ["waiting", "finished", undefined] — that undefined is the bug the flag names.

When to use it

  • Catching a missing return in a branch that was added later to an existing function.
  • Forcing every arm of a reducer or state machine to produce a value.
  • Pairing with noFallthroughCasesInSwitch to catch a forgotten break.
  • Making an unhandled union member a compile error via a throwing default case.

Frequently asked questions

How do I fix "Not all code paths return a value" (TS7030)?

Add a return on the branch that falls off the end, or widen the return type to include undefined if returning nothing is genuinely valid. The error means at least one path returns a value while another does not, so the function silently yields undefined on that path. A throwing default case is the usual fix inside a switch.

Why does my exhaustive switch still error under noImplicitReturns?

Because the value being switched on is not a finite union. TypeScript only treats a switch as exhaustive when it can enumerate every possible case — a discriminated union or a literal union type. If the subject is string, number or an enum widened to its base type, the compiler cannot prove the end is unreachable, so add a default that returns or throws.

Is noImplicitReturns included in strict?

No. strict enables noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables and alwaysStrict, but not noImplicitReturns, noFallthroughCasesInSwitch, noUnusedLocals or noUnusedParameters. Those are separate opt-ins you add on top.

More examples

Exhaustive switch: allowed, and how to keep it that way

Prints 9 and 3.1416. The default case assigns s to never, so adding a third member to Shape without handling it becomes a compile error at that line rather than a runtime surprise. Without the default, the function is still accepted while the union is fully covered — but you lose that early warning.

Example · typescript
type Shape =
  | { kind: "circle"; r: number }
  | { kind: "square"; side: number };

// No TS7030: every union member returns, so the end is unreachable.
function area(s: Shape): number {
  switch (s.kind) {
    case "circle":
      return Math.PI * s.r ** 2;
    case "square":
      return s.side ** 2;
    default: {
      const never: never = s;
      throw new Error(`unhandled shape: ${JSON.stringify(never)}`);
    }
  }
}

console.log(area({ kind: "square", side: 3 }));
console.log(area({ kind: "circle", r: 1 }).toFixed(4));

The fallthrough the second flag catches

describe(3) prints "divisible by three" and then returns "remainder one" — the fallthrough bug, visible in the output. case 2 and case -2 stacked with no statements between them are not flagged, because only non-empty cases must end in break, return or throw.

Example · typescript
function describe(n: number): string {
  switch (n % 3) {
    case 0:
      // TS7029 under noFallthroughCasesInSwitch: non-empty case with no break
      console.log("divisible by three");
    case 1:
      return "remainder one";
    case 2:
    case -2:            // empty cases stacked for grouping are fine
      return "remainder two";
    default:
      throw new Error("unreachable");
  }
}

console.log(describe(3)); // logs the message, then returns "remainder one"
console.log(describe(4));

Discussion

  • Be the first to comment on this lesson.