TL;DR
TypeScript can't figure out what this refers to inside your function. Three quick exits:
- Switch to an arrow function โ it inherits
thisfrom the surrounding scope automatically. - Declare a typed
thisparameter as the first argument of your function. - Use
.bind(this)after giving the method an explicitthistype.
When does this error appear?
The error fires when noImplicitThis (or strict mode) is enabled in tsconfig.json and TypeScript spots a this it can't statically resolve. Four situations trigger it constantly:
- A regular
functionused as a DOM event listener - A standalone callback passed to
forEach,setTimeout, etc. - A class method extracted and used as a callback elsewhere
- Object literal methods that reference
thisinside nested functions
// tsconfig.json
{
"compilerOptions": {
"strict": true // enables noImplicitThis among other checks
}
}
Root cause
JavaScript's this is determined by how a function is called, not where it's defined. That's the core problem. TypeScript can't trace runtime call sites, so when you write a plain function and use this inside, the compiler has no idea what object it'll be bound to. It refuses to guess.
// โ Error: 'this' implicitly has an 'any' type.
document.querySelector('button')?.addEventListener('click', function () {
console.log(this.textContent); // TypeScript: what is 'this' here?
});
// โ Same problem in a plain callback
const items = [1, 2, 3];
items.forEach(function (item) {
console.log(this); // Error: 'this' implicitly has an 'any' type.
});
Fix 1: Use an arrow function (most common fix)
Arrow functions have no this of their own. They grab it from the enclosing lexical scope โ which is exactly what you want inside a class method.
class FormHandler {
private label = 'Submit';
init() {
document.querySelector('button')?.addEventListener('click', () => {
// โ
'this' refers to the FormHandler instance
console.log(this.label);
});
}
}
Callbacks that don't use this at all? Arrow functions eliminate the ambiguity there too:
// โ
Clean โ no 'this', no problem
[1, 2, 3].forEach((item) => {
console.log(item);
});
Fix 2: Add an explicit this parameter
TypeScript supports a fake first parameter named this. It lets you declare the type without touching the function's real signature โ the compiler strips it entirely during compilation.
// โ
TypeScript now knows exactly what 'this' is
function handleClick(this: HTMLButtonElement, event: MouseEvent) {
console.log(this.textContent);
}
document.querySelector('button')?.addEventListener('click', handleClick);
This pattern shines when a handler is reused across multiple call sites. TypeScript enforces that callers bind it to the declared type โ mistakes get caught at compile time, not in production.
Fix 3: Explicit this in object literal methods
Here's a sneaky variant: a method in an object literal that passes a nested function as a callback. The outer method has correct context; the nested function loses it entirely.
// โ Nested function loses 'this'
const counter = {
count: 0,
start() {
setInterval(function () {
this.count++; // Error: 'this' implicitly has an 'any' type.
}, 1000);
}
};
// โ
Arrow function inherits 'this' from start()
const counter = {
count: 0,
start() {
setInterval(() => {
this.count++; // 'this' is the counter object
}, 1000);
}
};
Fix 4: Use .bind() with an explicit this type
Working with third-party code or older patterns? .bind() works, but TypeScript loses type information across bare .bind() calls. The solution: declare the this type on the method first, then bind.
class Tooltip {
message = 'Hello';
show(this: Tooltip) {
console.log(this.message);
}
register() {
// โ
Safe โ 'show' already carries an explicit 'this' type
document.addEventListener('mouseover', this.show.bind(this));
}
}
Fix 5: Disable noImplicitThis for legacy code (last resort)
Migrating a large JavaScript codebase to TypeScript? You can't always fix 200 files before shipping. A temporary escape hatch exists โ just don't treat it as permanent.
// tsconfig.json โ only if you have a migration plan
{
"compilerOptions": {
"strict": true,
"noImplicitThis": false
}
}
Track which files still need fixing (TypeScript's // @ts-check comments help), and re-enable the flag once the migration is done.
Verification
Run the compiler in check-only mode โ no output files generated, just error reporting:
# Check one file
npx tsc --noEmit src/your-file.ts
# Check the whole project
npx tsc --noEmit
No output means clean (exit code 0). Still seeing the error? Check scope carefully. A very common mistake: you fixed the outer function but left a nested function keyword inside it โ that inner function reintroduces the ambiguity.
Quick decision guide
- Inside a class method callback โ use an arrow function
- Standalone reusable handler โ add explicit
this: SomeTypeparameter - Object literal with nested callbacks โ arrow function for the nested callback
- Legacy codebase mid-migration โ set
noImplicitThis: falsetemporarily, track what's left

