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
| Code | TS7023 |
|---|---|
| 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 category | error |
| Listed under | Implicit 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
/** @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;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /**
* @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;
}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
returnexpression (directly or through another function) and has no@returnstag. - A variable is initialized from such a function and then returned from it (TS7022).
noImplicitAnyis on (part ofstrict).
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.
/**
* @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;
}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, whilereturn 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
- JSDoc Reference: @param and @returns · TypeScript handbook
- TSConfig Reference: noImplicitAny · TypeScript
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.