Quick answer
You called X(...) but X isn't a function at that point — it's undefined, an object, or something else non-callable. The three usual causes are a default/named import mismatch, a typo or wrong method for the value's real type, or a local variable shadowing the function you meant to call. Log typeof X right before the call to see what you actually have.
The exact error string
const result = data.calculate(5);
// Uncaught TypeError: data.calculate is not a function
import foo from "some-lib";
foo();
// Uncaught TypeError: foo is not a function
What it actually looks like
utils.format held the string "json". That is the distinction from ReferenceError: the name resolved fine, the value just wasn't callable.How to diagnose it in 30 seconds
Right before the failing call, log console.log(typeof X, X). The type tells you which cause applies: "undefined" — the import/export is mismatched or the property doesn't exist; "object" — you have the whole module or a data object instead of the function itself; a function, but the wrong one — a shadowing variable further down in scope.
Cause 1: default vs named export mismatch
// lib.js
export function formatDate(d) { /* ... */ } // named export, no default
// ❌ app.js
import formatDate from "./lib.js";
formatDate(); // TypeError: formatDate is not a function (imported the whole module)
// ✅ app.js
import { formatDate } from "./lib.js";
formatDate();
Cause 2: wrong method for the value's real type
const data = await res.json(); // API returned { items: [...] }, not an array
data.map(x => x.id);
// ❌ data.map is not a function — data is an object, not an array
data.items.map(x => x.id); // ✅ call the method on the actual array
Cause 3: a variable shadowing the function
import { map } from "lodash";
function process(map) { // ❌ parameter named "map" shadows the import
return map(items, fn); // TypeError: map is not a function (it's the array you passed)
}
function process(list) { // ✅ don't reuse the imported name
return map(list, fn);
}
Cause 4: calling before it's assigned (hoisting)
doWork();
const doWork = function () { /* ... */ };
// ❌ ReferenceError first (TDZ) if truly before; but with `var` you'd get
// "doWork is not a function" because var hoists the binding as undefined
var doWork = function () { console.log("done"); };
doWork(); // ✅ call after the assignment has run
Worked example: a broken build
This often shows up only after bundling, not in local dev:
// works in dev (unminified), fails in production:
import { helper } from "./utils";
helper();
// Uncaught TypeError: helper is not a function
// Check: does the built/minified bundle still export "helper"?
// Common causes: tree-shaking dropped an export it thought was unused,
// or a CommonJS/ESM interop mismatch changed the shape of the import.
Read the message as a path, not as a name
V8 prints the whole expression it tried to call, not just the final identifier, and that is the fastest diagnostic on the page. The part before the last dot is the value to inspect:
TypeError: utils.format is not a function
// ^^^^^ — inspect `utils`: it exists, but has no callable `format`
TypeError: res.data.map is not a function
// ^^^^^^^^ — inspect `res.data`: it is not an array
TypeError: _lodash.default.debounce is not a function
// ^^^^^^^^^^^^^^^^ — a transpiled interop wrapper: the import shape is wrong
That third form is worth recognising on sight. When the path contains .default, an underscore prefix, or a name you never wrote, you are looking at bundler interop output — which means the problem is how the module was imported (default vs named, CommonJS vs ESM), not the function itself.
The 10-second check
Log the container rather than the call. typeof alone is rarely enough, because the answer is usually "object" either way:
console.log(typeof utils, utils, Object.keys(utils ?? {}));
The key list is what resolves it. If the function you wanted appears nested under default, it is an import-shape problem. If the object is empty, the module never initialised — often a circular import. If the keys are there but the one you want is spelled differently, it is a rename or a version change. And if utils is undefined entirely, you have a different error waiting one line up.
Common variants at a glance
| Message | Likely cause | Fix |
|---|---|---|
X is not a function (import) | default/named export mismatch | match the import style to the export |
X.map is not a function | X isn't actually an array | check the real shape, index into the array field |
| your own function, still fails | a shadowing variable/param | rename the local binding |
| works in dev, fails in build | bundler dropped/renamed the export | inspect the built output |
module.exports.X is not a function | CommonJS/ESM interop mismatch | align require/import style |
Debugging checklist
- ✓ Log
typeof Xright before the call - ✓ Importing a library? Check default vs named export in its docs/source
- ✓ Calling a method on data? Confirm the value's real shape/type first
- ✓ Same name used nearby? Check for a shadowing parameter or variable
- ✓ Only fails after build? Inspect the bundled output for the export
- ✓ TypeScript catches most of this at compile time — consider adopting it for a library boundary
Frequently Asked Questions
What does 'X is not a function' mean?
You wrote X(...) but the value bound to X at that point is not callable — it's undefined, an object, a string, or anything else that isn't a function. JavaScript only discovers this at the moment of the call, so the error always points at the exact line where you tried to invoke it.
Why does importing a library trigger this?
The most common cause is a default vs named export mismatch: import foo from 'lib' when the library only exports named bindings (import { foo } from 'lib'), or the reverse. You end up with the whole module object (or undefined) bound to foo, and calling foo() fails because foo isn't a function.
Why does a method that should exist say it's not a function?
Usually the value isn't the type you think it is — an array method called on an object, a string method called on a number, or the value came from JSON.parse and turned out to be null or a plain object instead of the class instance with that method. Log typeof value and the value itself right before the call.
Why does my own function say it's not a function?
A local variable or parameter with the same name is shadowing your function — declaring let map = [] in the same scope as a map() you meant to call, for example. Check for a same-named variable, parameter, or loop index declared between the function definition and the call site.
Why does it happen after minifying or bundling?
A build step can tree-shake an export it thinks is unused, target the wrong module format (CommonJS vs ESM interop), or bundle a stale cached chunk. Confirm the function still exists in the built output, and that your import/export syntax matches the bundler's expected module format.
How do I fix 'X is not a function' fast?
Log typeof X right before the call to see what it actually is. If it's undefined, check the import/export shape and spelling. If it's an object, you likely have the wrong value (module object instead of function, or a shadowing variable). Fix the source of the binding, not the call site.
References
- TypeError: "x" is not a function (MDN)
- Function (MDN)
More JavaScript & runtime errors
Browse the full reference for JavaScript, Node.js, and database errors — exact message, cause, and fix.