"TypeError: x is not a function" means you wrote x(), but when that line ran, x held something other than a function: undefined, a string, an object, an array-like list or a Promise. To fix it, find the name just before "is not a function", log typeof for it on the line above, and work out why it isn't the function you expected. Usually the cause is a typo, a method that doesn't exist on that type, a value that replaced the function, or an import that returned something different.
The name in the message is your best clue. data.map is not a function tells you data exists but has no map method, so it probably isn't an array. MDN's reference page for the "is not a function" error lists the same family of mistakes.
I reproduced every error below myself. The scripts ran on Node.js 24.22.1, and the core cases gave byte-identical output on Node.js 18.20.8, 20.20.2, 22.23.4 and 26.11.1. The DOM examples ran in Chrome 156, and the TypeScript checks used tsc 7.0.2.
In this guide:
- What the error message is telling you
- How to find the cause in under a minute
- 1. A typo or wrong capitalisation in the method name
- 2. Calling an array method on something that isn't an array
- 3. You overwrote the function with a value
- 4. Calling a function expression before it is assigned
- 5. A missing semicolon before ( or [
- 6. Import and export don't match
- 7. Forgetting await on an async function
- 8. this points somewhere else inside a callback
- 9. A callback argument that is missing or isn't a function
- 10. The method doesn't exist in your Node.js or browser version
- Quick reference: causes and fixes
- Prevent it: TypeScript, optional chaining and typeof checks
- Wrapping up
- Frequently asked questions
What the error message is telling you
V8, the engine in Chrome and Node.js, prints the expression you tried to call. That makes the message more useful than it first looks. I ran seven broken calls through one script:
const api = { user: { getName: 'Ana' }, list: () => ({}) };
const notFn = 42;
TypeError: api.user.getName is not a function
TypeError: api.list(...).map is not a function
TypeError: notFn is not a function
TypeError: notFn is not a function or its return value is not iterable
TypeError: api.user.missing is not a function
TypeError: Cannot read properties of undefined (reading 'call')
| Message shape | What it means |
|---|
obj.name is not a function | obj exists, but obj.name is not a function (often undefined, or a string) |
fn(...).name is not a function | The return value of fn() has no such method |
x is not a function or its return value is not iterable | You destructured the result, as in const [a, b] = x(), and x isn't callable |
Cannot read properties of undefined (reading 'name') | A different error: the object before the dot is undefined |
That last row trips a lot of people up. If the thing before the dot is missing, you get "Cannot read properties of undefined" instead, which I covered in Cannot read properties of undefined: causes and fixes.
How to find the cause in under a minute
- Read the name. Copy everything before "is not a function". That expression is the thing that isn't callable.
- Jump to the line. The first stack frame (for example
c05_asi.js:2:1) gives the file, line and column. - Log the type on the line above the call.
console.log(typeof x, x) tells you at once whether you have undefined, a string, an object or a Promise. - Match it to a cause below.
undefined usually points to a typo or a missing export. An object points to a wrong method or a missing await.
typeof plugin.init: undefined
Here's what that check looked like for an object that simply has no init method. Once you know the type, the fix is usually obvious.
1. A typo or wrong capitalisation in the method name
JavaScript is case-sensitive, so toUppercase and toUpperCase are different properties. The misspelled one is undefined, and calling undefined throws:
const name = 'mahir';
try { console.log(name.toUppercase()); } catch (e) { console.log(e.constructor.name + ': ' + e.message); }
console.log(name.toUpperCase());
TypeError: name.toUppercase is not a function
MAHIR
The DOM has its own classic typos. Both of these came from Chrome 156:
TypeError: document.getElementByID is not a function
TypeError: document.getElementById(...).addEventlistener is not a function
Fix: check the exact spelling. It's getElementById (lowercase "d") and addEventListener (capital "L"). Your editor's autocomplete, or TypeScript (see below), catches these before you run anything.
2. Calling an array method on something that isn't an array
map, filter and forEach live on arrays. API responses often wrap the array inside an object, and plain objects and strings don't have those methods:
const data = { users: [{ name: 'Ana' }, { name: 'Ben' }] };
const scores = { ana: 9, ben: 7 };
const csv = 'a,b,c';
TypeError: data.map is not a function
TypeError: scores.forEach is not a function
TypeError: csv.map is not a function
Fix: reach the actual array, or convert first:
console.log(data.users.map(u => u.name));
console.log(Object.entries(scores).map(([k, v]) => `${k}=${v}`).join(', '));
console.log(csv.split(',').map(x => x.toUpperCase()));
[ 'Ana', 'Ben' ]
ana=9, ben=7
[ 'A', 'B', 'C' ]
Array.isArray(data): false | Array.isArray(data.users): true
In the browser, querySelectorAll() returns a NodeList and getElementsByClassName() returns an HTMLCollection. Neither is a real array:
TypeError: document.querySelectorAll(...).map is not a function
TypeError: document.getElementsByClassName(...).forEach is not a function
typeof document.querySelectorAll('.item').forEach -> "function"
Array.from(document.querySelectorAll('.item')).map(li => li.textContent) -> ["One","Two"]
[...document.getElementsByClassName('item')].map(li => li.textContent) -> ["One","Two"]
A NodeList does have forEach, but not map or filter. An HTMLCollection has neither. Wrap either one in Array.from() or spread it into [...]. If you're new to these methods, my guide to map, filter and reduce shows what each one returns.
3. You overwrote the function with a value
A function name is just a variable. Assign something else to it and the function is gone. This one is easy to write when a result and a function share a name:
function total(items) { return items.reduce((s, n) => s + n, 0); }
let prices = [5, 10];
total = total(prices); // the function is now the number 15
TypeError: total is not a function
sumOfPrices = 15 | sum still function
Fix: give results and functions different names, such as sum() and sumOfPrices. Declaring functions with const also helps, because reassigning a const throws straight away at the line that caused the damage, not later at the call.
The same thing happens with globals. A variable called name, status or close in a browser script can clash with built-in window properties.
4. Calling a function expression before it is assigned
Function declarations are hoisted completely, so you can call them above where they appear. A function expression stored in var is not. Only the variable is hoisted, holding undefined:
try { greet('Ana'); } catch (e) { console.log('TypeError: ' + e.message); }
var greet = function (n) { return 'Hi ' + n; };
console.log(hello('Ben'));
function hello(n) { return 'Hello ' + n; }
TypeError: greet is not a function
Hello Ben
With const or let, the same mistake gives a clearer error:
ReferenceError: Cannot access 'greet' before initialization
Fix: move the call below the assignment, or use a function declaration. My post on var vs let vs const explains hoisting and the temporal dead zone in more detail.
5. A missing semicolon before ( or [
Automatic semicolon insertion doesn't add a semicolon before a line that starts with ( or [. JavaScript glues the two lines together and tries to call the previous value:
const config = { debug: true }
(function () { console.log('setup ran'); })()
TypeError: {(intermediate value)} is not a function
The same thing happens with an array literal on the next line. This one reads "hello"[1, 2] as an index lookup, gets the character "l", and then calls forEach on it:
const greeting = 'hello'
[1, 2].forEach(n => console.log(n))
TypeError: "hello"[(1 , 2)].forEach is not a function
Fix: end the previous statement with a semicolon. If your style skips semicolons, start lines beginning with ( or [ with ;. A formatter like Prettier also handles this for you.
const config = { debug: true };
(function () { console.log('setup ran', config.debug); })();
setup ran true
MDN's lexical grammar page lists exactly where semicolons get inserted and where they don't.
6. Import and export don't match
A very common cause in Node.js projects is destructuring a named export from a module that exports a single function, or the reverse:
module.exports = formatPrice;
exports.formatPrice = (n) => '£' + n.toFixed(2);
const { formatPrice } = require('./mods/format');
const formatPrice2 = require('./mods/named');
TypeError: formatPrice is not a function
TypeError: formatPrice2 is not a function
format.js exports a function | named.js exports an object
In ES modules, calling a namespace import (import * as utils) as if it were the function fails the same way. A missing default export fails earlier, at load time, with a SyntaxError:
TypeError: utils is not a function
SyntaxError: The requested module './utils.mjs' does not provide an export named 'default'
Fix: open the module and check what it really exports, then match it: require('./format') for module.exports = fn, and { formatPrice } for named exports. Logging typeof require('./module') settles it in one line.
Circular imports cause a sneakier version. When two CommonJS files require each other, one of them gets an unfinished exports object while it loads:
TypeError (during load): a.hello is not a function
(node:154628) Warning: Accessing non-existent property 'hello' of module exports inside circular dependency
Node.js prints that warning to help. The fix is to move the shared code into a third module, or to export before you require. The Node.js modules documentation has a section on cycles.
7. Forgetting await on an async function
An async function always returns a Promise, even when the code inside returns an array. Without await, you call array methods on the Promise:
async function getUsers() { return [{ name: 'Ana' }, { name: 'Ben' }]; }
const users = getUsers();
TypeError: users.map is not a function
users is [object Promise]
The reverse happens with fetch(): once you've awaited res.json(), the result is plain data, so calling .json() on it again fails. I used a small stand-in object for the Response so the test runs offline:
const body = await res.json();
TypeError: body.json is not a function
Fix: await the call inside an async function (or use .then()), and only call .json() on the Response object. My async/await tutorial covers the patterns step by step.
[ 'Ana', 'Ben' ]
8. this points somewhere else inside a callback
A regular function callback gets its own this. In sloppy-mode scripts, that this is the global object, which has no double method:
doubledOld() { return this.items.map(function (n) { return this.double(n); }); },
doubledNew() { return this.items.map((n) => this.double(n)); },
TypeError: this.double is not a function
[ 4, 6 ]
In strict mode or ES modules, this is undefined instead, so the identical code throws a different error:
TypeError: Cannot read properties of undefined (reading 'double')
Fix: use an arrow function, which keeps the outer this, as doubledNew() does. When you pass a method as a callback, for example to addEventListener or setTimeout, bind it with this.method.bind(this) or wrap it in an arrow function.
9. A callback argument that is missing or isn't a function
If a function expects a callback and you forget to pass one, or you pass a string by mistake, the error points inside that function, which can be confusing:
function loadData(onDone) {
const data = [1, 2, 3];
onDone(data);
}
TypeError: onDone is not a function
TypeError: onDone is not a function
Both loadData() and loadData('done') failed with the same message. Fix: pass a real function. In your own code, give the parameter a default or check it up front so the error explains itself:
function loadSafe(onDone = () => {}) { onDone([1, 2, 3]); return 'ok'; }
if (typeof onDone !== 'function') throw new TypeError(`onDone must be a function, got ${typeof onDone}`);
ok
TypeError: onDone must be a function, got string
10. The method doesn't exist in your Node.js or browser version
Newer built-ins simply don't exist on older runtimes. Your code is correct, but the method is undefined. I ran the same script on five Node.js versions:
v18.20.8 Object.groupBy: TypeError: Object.groupBy is not a function
v18.20.8 toSorted: TypeError: nums.toSorted is not a function
v20.20.2 Object.groupBy: TypeError: Object.groupBy is not a function
v20.20.2 toSorted: 1,2,3
v22.23.4 Object.groupBy: red,blue
| Method | Node 18.20.8 | Node 20.20.2 | Node 22.23.4, 24.22.1, 26.11.1 |
|---|
Object.groupBy() | is not a function | is not a function | works |
Array.prototype.toSorted() | is not a function | works | works |
Set.prototype.union() | is not a function | is not a function | works |
Fix: run node -v and upgrade, or use an older equivalent ([...nums].sort() instead of toSorted(), reduce() instead of Object.groupBy()). For browser code, check the compatibility table at the bottom of the MDN page, such as Object.groupBy(), or add a polyfill.
Quick reference: causes and fixes
| # | Cause | Typical message | Fix |
|---|
| 1 | Typo or wrong case | name.toUppercase is not a function | Fix the spelling, use autocomplete |
| 2 | Not an array | data.map is not a function | Use the inner array, Object.entries(), Array.from() |
| 3 | Function overwritten | total is not a function | Separate names, const |
| 4 | Called before assignment | greet is not a function | Move the call, or use a declaration |
| 5 | Missing semicolon | {(intermediate value)} is not a function | Add ; before ( or [ |
| 6 | Import/export mismatch | formatPrice is not a function | Match named vs default export |
| 7 | Missing await | users.map is not a function | await the Promise |
| 8 | Lost this | this.double is not a function | Arrow function or bind() |
| 9 | Missing callback | onDone is not a function | Pass a function, add a default |
| 10 | Old runtime | Object.groupBy is not a function | Upgrade or use an older method |
Prevent it: TypeScript, optional chaining and typeof checks
TypeScript catches the first and third causes before the code ever runs. Here's what tsc 7.0.2 reported for a typo and for calling a number:
check.ts(2,11): error TS2551: Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
check.ts(4,1): error TS2349: This expression is not callable.
Type 'Number' has no call signatures.
For functions that might genuinely be missing, such as optional plugin hooks, use optional chaining with a call: plugin.init?.() calls init only if it exists, and returns undefined otherwise. A typeof check does the same thing more explicitly:
console.log('optional call:', plugin.init?.());
if (typeof plugin.init === 'function') plugin.init(); else console.log('init skipped: not a function');
optional call: undefined
init skipped: not a function
Don't wrap every call like this, though. If a function should exist, a loud error is more useful than silently skipping it. Keep ?.() for things that are optional by design.
Wrapping up
"x is not a function" always comes down to one question: what is x right now? Log its type, read the name in the message, and the cause is usually one of the ten above. The three I run into most often are a missing await, a NodeList treated as an array, and an import that returns something different from what I expected.
If you like small, practical browser utilities, have a look at my free tools.
Frequently asked questions
What does "TypeError: x is not a function" mean?
It means you used parentheses to call something that isn't a function when that line ran. The value might be undefined, a string, an object, an array-like list or a Promise. Log typeof x just before the call to see which.
Why do I get "map is not a function"?
The value isn't an array. It is often an object that wraps the array (use data.items.map), a string (split it first), a NodeList (use Array.from), or a Promise you forgot to await.
How is this different from "Cannot read properties of undefined"?
"x.y is not a function" means x exists but x.y isn't callable. "Cannot read properties of undefined (reading 'y')" means x itself is undefined, so JavaScript can't even look up y.
Why does querySelectorAll().map not work?
querySelectorAll returns a NodeList, not an array. It has forEach but no map or filter. Convert it with Array.from(list) or [...list] first.
Why does a missing semicolon cause "is not a function"?
When the next line starts with ( or [, JavaScript doesn't insert a semicolon, so it joins the two lines and tries to call the previous value. Adding a semicolon at the end of the previous line fixes it.
Why does Object.groupBy say it is not a function?
Object.groupBy only exists in newer runtimes. In my tests it failed on Node.js 18.20.8 and 20.20.2 and worked on 22.23.4 and later. Upgrade Node.js or group with reduce().
Can try...catch fix "is not a function"?
It can stop the crash, but it doesn't fix the cause. Use try...catch to handle errors you expect, and fix the underlying typo, import or missing await instead of hiding it.
Does TypeScript prevent this error?
It prevents most of it. TypeScript flags misspelled methods (TS2551) and calls on non-callable values (TS2349) at compile time, but it can't help with untyped data from an API or with code that bypasses the types using any.
Last checked: 3 November 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.