TypeScript error

TS7023: A recursive function needs a return type

  • Error TS7023
  • Reported with noImplicitAny
  • Reported with strict
  • Tested with TypeScript 5.9

error TS7023: 'depth' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. is reported for a recursive function whose return type the checker would have to know in order to work it out. To infer depth's return type it needs the type of depth(node.parent) + 1, which needs depth's return type.

TS7022 is the same circle through a variable: in the retry example retried holds the result of fetchWithRetry, whose return type depends on retried. One @returns tag on the function breaks both.

At a glance

CodeTS7023
Message'{0}' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
Also on this page: TS7022 '{0}' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
TypeScript's categoryerror
Listed underImplicit any
Turned on by noImplicitAny, strict
In .js files Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file

The messages are TypeScript's own text from its diagnostic messages file (Apache-2.0); {0} is filled in by tsc for each error.

What it looks like

tree.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: tree.js(3,17): error TS7023: 'depth' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. tree.js(3,17): error TS7023: 'depth' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. tree.js(3,17): error TS7023: 'depth' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. checked 21 October 2026
/** @param {{ parent?: object }} node */
export function depth(node) {error TS7023: 'depth' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
  return node.parent ? depth(node.parent) + 1 : 0;
}
A recursive function with no return type.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
fetch.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: fetch.js(6,23): error TS7023: 'fetchWithRetry' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. fetch.js(10,9): error TS7022: 'retried' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. fetch.js(6,23): error TS7023: 'fetchWithRetry' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. fetch.js(10,9): error TS7022: 'retried' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. fetch.js(6,23): error TS7023: 'fetchWithRetry' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. fetch.js(10,9): error TS7022: 'retried' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. checked 21 October 2026
/**
 * @param {string} url
 * @param {number} attempt
 */
export async function fetchWithRetry(url, attempt = 1) {error TS7023: 'fetchWithRetry' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
  const response = await fetch(url);
  if (response.ok || attempt === 3) return response;
  const retried = await fetchWithRetry(url, attempt + 1);error TS7022: 'retried' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
  return retried;
}
A retry loop: the function and the variable that holds its result.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

What causes it

  • A function calls itself in a return expression (directly or through another function) and has no @returns tag.
  • A variable is initialized from such a function and then returned from it (TS7022).
  • noImplicitAny is on (part of strict).

How to fix it

Add @returns to the function

With @returns {number} (or @returns {Promise<Response>} for the async retry), the checker no longer has to infer the return type, and the variable that holds the result takes its type from the tag.

fetch.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 {{ parent?: object }} node
 * @returns {number}
 */
export function depth(node) {
  return node.parent ? depth(node.parent) + 1 : 0;
}

/**
 * @param {string} url
 * @param {number} attempt
 * @returns {Promise<Response>}
 */
export async function fetchWithRetry(url, attempt = 1) {
  const response = await fetch(url);
  if (response.ok || attempt === 3) return response;
  const retried = await fetchWithRetry(url, attempt + 1);
  return retried;
}
A @returns tag breaks the circle.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Gotchas

  • Some recursive functions pass without the tag. Checked here with TypeScript 5.9, if (!node.parent) return node; return root(node.parent); was accepted, while return n <= 1 ? 1 : n * factorial(n - 1); was TS7023. The tag is the reliable fix, and it documents the function.
  • The error is reported on the function name, not on the recursive call.

Sources

Last reviewed 20 October 2026.