Learn why TypeScript throws TS7022 for circular type inference that results in implicit any, and how to break the cycle with explicit annotations.
TS7022 fires when TypeScript's type inference engine hits a circular dependency. To figure out the type of a variable, TypeScript looks at its initializer — but if that initializer references the variable itself (directly or through other variables), TypeScript enters an infinite loop. Rather than hanging, it gives up and falls back to any.
Under noImplicitAny (part of strict mode), falling back to any is an error. TypeScript is telling you: "I can't figure out this type on my own — you need to tell me."
// The general shape of the error:
// 'result' implicitly has type 'any' because it does not have a type annotation
// and is referenced directly or indirectly in its own initializer.The simplest case — a variable's initial value depends on itself through a function call.
// ❌ Broken
const config = {
debug: false,
logger: {
log(message: string) {
if (config.debug) {
//~~~~~~ Error: 'config' implicitly has type 'any' because it is
// referenced directly or indirectly in its own initializer.
console.log(message)
}
},
},
}// ✅ Fixed — add an explicit type annotation
interface AppConfig {
debug: boolean
logger: {
log(message: string): void
}
}
const config: AppConfig = {
debug: false,
logger: {
log(message: string) {
if (config.debug) {
console.log(message)
}
},
},
}Two variables whose types depend on each other create an indirect circular reference.
// ❌ Broken
const parser = {
parse(input: string) {
return validator.validate(input)
// ~~~~~~~~~ Error: 'validator' implicitly has type 'any'
},
}
const validator = {
validate(input: string) {
return parser.parse(input)
// ~~~~~~ Error: 'parser' implicitly has type 'any'
},
}// ✅ Fixed — annotate both variables to break the cycle
interface Parser {
parse(input: string): boolean
}
interface Validator {
validate(input: string): boolean
}
const parser: Parser = {
parse(input: string) {
return validator.validate(input)
},
}
const validator: Validator = {
validate(input: string) {
return parser.parse(input)
},
}Object methods or getters that reference the containing object during initialization cause the same issue.
// ❌ Broken
const cart = {
items: [{ name: "Widget", price: 25 }],
get total() {
return cart.items.reduce((sum, item) => sum + item.price, 0)
// ~~~~ Error: 'cart' implicitly has type 'any'
},
}// ✅ Fixed — use 'this' instead of referencing the variable by name
const cart = {
items: [{ name: "Widget", price: 25 }],
get total() {
return this.items.reduce((sum, item) => sum + item.price, 0)
},
}Add an explicit type annotation. This is the primary fix. When TypeScript knows the type up front, it doesn't need to infer it from the circular initializer:
const value: number = computeValue()Use this instead of the variable name. If an object method references itself, use this so TypeScript doesn't need to resolve the outer variable's type:
const obj = {
data: [1, 2, 3],
sum() { return this.data.reduce((a, b) => a + b, 0) }
}Extract the circular part into a separate function. Moving the logic out of the initializer can break the cycle:
function createLogger(getDebug: () => boolean) {
return { log: (msg: string) => getDebug() && console.log(msg) }
}
const config = {
debug: false,
logger: createLogger(() => config.debug),
}Define interfaces for the structure. For complex objects with cross-references, defining the shape up front with interfaces is the cleanest solution.
TS7022 occurs when TypeScript's type inference hits a circular dependency. TypeScript determines types by examining how values are used and assigned — but when a variable's type depends on itself, there's no starting point for inference. TypeScript breaks the cycle by assigning any, and under noImplicitAny, that implicit any becomes an error.
The fix is always the same: give TypeScript the type information it can't infer. Add an explicit type annotation to the variable (or variables) involved in the cycle. Once TypeScript knows the type, it no longer needs to look at the initializer to figure it out, and the circular dependency is broken.
A circular reference occurs when resolving the type of variable A requires knowing the type of variable B, and resolving B requires knowing A. This can happen directly (a variable's initializer references itself) or indirectly through a chain of dependencies. Common patterns include object literals with methods that reference the object by name, mutually recursive variables, and getter properties that access the parent object.
Get the latest TypeScript tips, tutorials, and course updates delivered straight to your inbox.