BugMojoBugMojoBugMojo
FeaturesPricingBlogHelpAbout
Add to ChromeLog inGet started
BugMojoBugMojo

Bug reports that actually help fix bugs — capture, replay, share.

A product of Softech Infra.

Product

  • Features
  • Pricing
  • Browser extension
  • Get started
  • Log in

Resources

  • Help & guides
  • Blog
  • Compare
  • Glossary

Company

  • About
  • Contact
  • Security
  • Privacy
  • Terms
  • Sitemap
© 2026 BugMojo. All rights reserved.
AllGuidesEngineeringPlaybooksCompareGlossaryAlternativesBy roleBug tracking by framework
  1. Home
  2. Blog
  3. Guides
  4. How to Fix "x is not a function" in JavaScript
Guide

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.

Hrishikesh BaidyaHrishikesh Baidya·Jul 16, 2026·9 min read
Guides
Isometric line-art browser console showing a TypeError that a value is not a function, highlighted in red beside a lime fix, on a dark canvas
TL;DR
  • What it means: you used call syntax — x() — on a value that isn't a function. The value was undefined, a string, a number, an object, or a different property than you meant.
  • Most common causes: a typo or wrong property name, a missing or renamed import, a wrong destructure, a value that's actually data (not a method), an array method on a non-array, or a library that hasn't loaded yet.
  • The subtle one: a method passed as a callback loses its this, so a this.something() call inside it throws. Fix with an arrow function or .bind().
  • Find it fast: log typeof x right before the failing line — if it isn't 'function', that's your answer.

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().

1-typo-or-wrong-name.jsjavascript
// 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.

2-bad-import-or-destructure.jsjavascript
// 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.

3-value-is-data.jsjavascript
// 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);
Tip

When an array method throws is not a function, the value is almost never an array. console.log(Array.isArray(x), x) tells you instantly — a wrapped response object, a NodeList, or a single item where you expected a list are the usual culprits.

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.

4-lost-this-broken.jsjavascript
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.

5-lost-this-fixed.jsjavascript
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);
  }
}

A tell for the this case: the error only appears when the method runs as a callback, never when you call obj.method() directly. If it works one way and throws the other, suspect a lost binding.

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.

6-not-loaded-yet.jsjavascript
// 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.

7-diagnose-with-typeof.jsjavascript
// 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/null

That 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.

Tip

This is where a captured session earns its keep. If a tool records the console and network for the exact report, you see the real value at call time — the undefined import, the object where you expected an array, the API response that came back a shape early — instead of re-deriving it from a stack trace. The console log is the diagnosis; you just need it from the machine where it actually broke.

Choosing the right fix

Match the fix to the cause rather than reaching for one tool everywhere:

  • Correct the name or import — when typeof is '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, not response). 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.
Key takeaway

The honest takeaway: x is not a function is a message about a value's type, not a diagnosis. The parentheses are fine — the value wasn't callable. Log typeof x right before the line to learn what it really was, then pick the fix that matches: correct the name or import, reach into the right value, restore a lost this, or use an optional call for a truly optional method. The this case is the one that hides; check whether the method works called directly but fails as a callback.

⁓ ⁓ ⁓
See what the value actually was

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 extension

Frequently asked questions

Frequently asked questions

Sources

  1. TypeError: "x" is not a function — MDN reference: thrown when a value is called but is not a function — MDN Web Docs (2026)
  2. TypeError — MDN reference: thrown when an operation is performed on a value of the wrong type — MDN Web Docs (2026)
  3. 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)
  4. Optional chaining (?.) — MDN reference: the optional-call form obj.method?.() invokes only when the method exists — MDN Web Docs (2026)
Share:
Hrishikesh Baidya
Hrishikesh Baidya· Chief Technology Officer

Hrishikesh Baidya is the CTO at Softech Infra. He is drawn to architecture that is invisible — systems that simply work — and leads the engineering behind BugMojo.

On this page

  • What the error actually means
  • Cause 1: a typo or wrong property name
  • Cause 2: the value is undefined (bad import or destructure)
  • Cause 3: the value is data, not a method
  • Cause 4: the lost `this` binding (the subtle one)
  • Cause 5: the dependency hasn't loaded yet
  • Find it fast: check the real type before you call
  • Choosing the right fix

Get bug-tracking insights, weekly.

Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.