How to Fix "x is not a function" in JavaScript
This TypeError means you called something that isn't a function. Here is every common cause — from a simple typo to a lost `this` binding — with broken and fixed code for each, and a reliable way to find which one you hit.
TypeError: x is not a function is JavaScript telling you something blunt: you put parentheses after a value, and that value can't be called. The engine evaluated x, checked whether it was callable, and it wasn't. The exact wording varies — undefined is not a function, foo.bar is not a function, x.map is not a function — but the shape is always the same, and so is the debugging move: find out what that value really was at the moment the line ran, because it was almost never what you assumed.
What the error actually means
x is not a function is a TypeError thrown when your code uses call syntax — the parentheses in x() — on a value that is not a function. The value might be undefined, a string, a number, an object, or simply a different property than the one you intended. JavaScript names what you tried to call, not why it was the wrong type — that has to be traced.
Every instance reduces to the same thing: x() where x was not callable. So the real question is never the parentheses — it's what x resolved to. The causes below are, roughly, the order in which they show up in real bug reports, and each has a broken version and the fix that matches it.
Cause 1: a typo or wrong property name
The most common cause is the most boring one: you spelled the method wrong, or the property exists under a slightly different name. Array methods and string methods are frequent victims — filter vs filert, toLowerCase vs toLowercase, length (a number) called as if it were length().
// Broken: method name is misspelled, and length is a property, not a method
const names = users.mapp(u => u.name); // TypeError: users.mapp is not a function
const count = users.length(); // TypeError: users.length is not a function
// Fixed: correct spelling; length is read, not called
const names = users.map(u => u.name);
const count = users.length;Cause 2: the value is undefined (bad import or destructure)
If x is undefined, calling it reads as undefined is not a function. The usual sources are imports and destructuring. A default import that was really a named export, a renamed or moved function, or a destructure of a property the module doesn't expose all leave you holding undefined — and you only find out when you try to call it.
// Broken: formatDate is a named export, imported as if it were the default
import formatDate from './date-utils';
formatDate(order.createdAt); // TypeError: formatDate is not a function
// Broken: the module has no `debounce` — the destructure yields undefined
const { debounce } = require('./helpers');
debounce(onScroll, 200); // TypeError: debounce is not a function
// Fixed: import the name the module actually exports
import { formatDate } from './date-utils';
formatDate(order.createdAt);This is the same family of bug as Cannot read properties of undefined — a value that should have been there wasn't. The difference is only the syntax that exposed it: property access there, a function call here. If the import resolves to undefined, the fastest confirmation is one log line, covered below.
Cause 3: the value is data, not a method
Sometimes x exists and is perfectly valid — it's just a string, a number, or a plain object, none of which are callable. This happens when a property name collides with a method you expected, or when an API returns a field as a value where you assumed a function.
// Broken: `status` is a string field, not a function
const label = order.status(); // TypeError: order.status is not a function
// Broken: calling an array method on something that isn't an array
// (a paginated API returns { items: [...] }, not a bare array)
const rows = response.map(r => r.id); // TypeError: response.map is not a function
// Fixed: read the field; reach into the array that actually exists
const label = order.status;
const rows = response.items.map(r => r.id);Cause 4: the lost `this` binding (the subtle one)
This is the cause that sends people in circles, because the method exists, the object exists, and the code looks right. The trap is that in JavaScript, this is decided by how a function is called, not where it's defined. The instant you pass a method somewhere as a plain function value — to onClick, setTimeout, addEventListener, .then(), an array callback — it gets detached from its object. When it later runs, this is no longer that object, so any this.helper() inside it throws this.helper is not a function.
class Uploader {
constructor() {
this.retries = 0;
}
logRetry() {
console.log('retry', this.retries);
}
start() {
// `this.logRetry` is passed as a bare function value — it loses `this`.
// When the timer fires, `this` is undefined, so this.logRetry throws.
setTimeout(this.logRetry, 1000);
// TypeError: Cannot read properties of undefined (reading 'retries')
// — and passing this.missing the same way gives "is not a function"
}
}
new Uploader().start();There are three reliable fixes, and the one you choose depends on the shape of your code. An arrow function wrapper preserves the surrounding this because arrows don't get their own binding; it's the least invasive. .bind() permanently pins this to the object and returns a new function. A class field arrow method bakes the binding in once at construction, which is the cleanest when the method is used as a callback in many places.
class Uploader {
retries = 0;
// Fix A: class field arrow — `this` is bound once, for every use site
logRetry = () => {
console.log('retry', this.retries);
};
start() {
// Fix B: arrow wrapper keeps the surrounding `this`
setTimeout(() => this.logRetry(), 1000);
// Fix C: .bind() returns a permanently bound copy
setTimeout(this.logRetry.bind(this), 1000);
}
}Cause 5: the dependency hasn't loaded yet
If a library attaches itself to a global (an older analytics snippet, a payment SDK, a charting script) and your code runs before the <script> finishes loading, the global is still undefined when you call it. The classic historical version of this — $ is not a function before jQuery loaded — is the same bug: call ordering, not the code itself.
// Broken: analytics.track runs before the SDK script has loaded
window.analytics.track('signup'); // TypeError: window.analytics.track is not a function
// Fixed: wait for the dependency, then call it
function whenReady(check, fn) {
if (check()) return fn();
setTimeout(() => whenReady(check, fn), 50);
}
whenReady(
() => window.analytics && typeof window.analytics.track === 'function',
() => window.analytics.track('signup'),
);Find it fast: check the real type before you call
Across every cause above, one habit collapses the guesswork: log the type right before the failing line. typeof returns 'function' only for callable values, so anything else is your answer in a single line.
// Put this immediately before the line that throws
console.log(typeof handler, handler);
// 'undefined' -> bad import, wrong destructure, or dependency not loaded yet
// 'string' | 'number' -> it's data, not a method (Cause 3)
// 'object' -> maybe a wrapped response; check the property you meant
// 'function' -> the value is fine; the bug is `this` or the argument shape
// For a maybe-absent optional callback, call it defensively:
handler?.(); // runs only if handler exists — never throws on undefined/nullThat last line is the optional-call form of optional chaining: obj.method?.() invokes the method only when it exists and short-circuits to undefined otherwise. It's the correct fix for a genuinely optional callback — an onChange a parent may not pass, a plugin hook that may be absent. It is not a fix for a value of the wrong type, and it shouldn't be used to hide a method that's supposed to be there; in that case, find out why it's missing instead.
When the failing line is buried in a bundle and the error only happens for some users, reading the stack trace gets you to the right file — but not to what the value was. That's the gap: the trace shows where x() ran, not that x was an object instead of a function. Reproducing it locally often fails because your imports resolve, your SDKs are loaded, and your fixtures return the shape you expect.
Choosing the right fix
Match the fix to the cause rather than reaching for one tool everywhere:
- Correct the name or import — when
typeofis'undefined'because of a typo, a default-vs-named import, or a wrong destructure. Fix the source; don't guard past it. - Reach into the real value — when the value is data or a wrapped object (
response.items, notresponse). The method exists; you were pointing at the wrong thing. - Restore
this— when a method runs as a callback. Use an arrow wrapper, a class field arrow, or.bind(). This is the subtle case; suspect it whenever the method works called directly but throws as a callback. - Optional call (
?.()) — only when the method is genuinely allowed to be absent. Pair it with a fallback so nothing silently no-ops. - Wait for the dependency — when a global loads asynchronously. Fix the ordering; don't retry blindly.
Install the free BugMojo extension and capture the exact session — replay, console, and network — when the error happens, so you see the undefined import or wrong-typed value at call time instead of guessing from a stack trace.
Install the free extensionFrequently asked questions
Frequently asked questions
Sources
- TypeError: "x" is not a function — MDN reference: thrown when a value is called but is not a function — MDN Web Docs (2026)
- TypeError — MDN reference: thrown when an operation is performed on a value of the wrong type — MDN Web Docs (2026)
- this — MDN reference: how the value of this is determined at call time, and how it is lost when a method is detached — MDN Web Docs (2026)
- Optional chaining (?.) — MDN reference: the optional-call form obj.method?.() invokes only when the method exists — MDN Web Docs (2026)
Get bug-tracking insights, weekly.
Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.

