"x is not a function" in JavaScript: causes and fixes
The value in front of the parentheses is not callable. Six causes, including the missing semicolon that turns an array into a function call.

x is not a function means JavaScript found a ( and tried to call whatever was immediately to its left, but that value is not callable. It is a TypeError, thrown at runtime, and the name in the message is the exact expression that failed.
The fix is always the same shape: work out what that value actually holds, then work out why it holds that instead of a function.
const user = { name: "Ada" };
user.map((u) => u);
// TypeError: user.map is not a function
Objects do not have .map(). Arrays do. Every output below came from Node v24.14.1.
This error has a close relative that is easy to confuse it with. x is not a function means the value exists but cannot be called. Cannot read properties of undefined means the value does not exist at all, so there was nothing to reach into. If you are not sure which one you are looking at, the word after TypeError: decides it.
Start by printing the value
Before theorising, look at what you are calling:
console.log(typeof user.map, user.map);
typeof returns "undefined" when the property does not exist at all, and "string", "number" or "object" when something is there but is the wrong kind of thing. Those two cases have different causes, so this one line splits the problem in half immediately.
Six causes you can spot from the value itself
1. The method belongs to a different type. map, filter, forEach and push are array methods. Calling them on a plain object throws, and so does calling a number method on a string:
const n = "5";
n.toFixed(2);
// TypeError: n.toFixed is not a function
"5" looks like a number in the console. It is not one. This is the usual outcome of reading a value from a form field, a URL parameter, or localStorage, all of which hand back strings.
2. The name is misspelled, usually the capitalisation. JavaScript method names are case sensitive and there is no fuzzy matching:
const s = "abc";
s.toUppercase();
// TypeError: s.toUppercase is not a function
The method is toUpperCase, with a capital C. The same trap catches getElementByID (the real name ends in Id) and toJson (it is toJSON).
3. Something overwrote the function. A name that held a function earlier can hold something else by the time you call it:
let format = (x) => x;
format = "hello";
format(1);
// TypeError: format is not a function
This is worth checking whenever the code used to work. const prevents it outright, which is a good reason to prefer it.
4. The import is not shaped the way you think. A module with a default export puts everything one level down:
const mod = { default: { run() { return 1; } } };
mod.run();
// TypeError: mod.run is not a function
Logging the whole module object tells you immediately whether you want mod.run or mod.default.run.
5. It is array-like, not an array. Values with a length and numeric keys look like arrays and are not:
const fake = { length: 2, 0: "a", 1: "b" };
fake.forEach((x) => x);
// TypeError: fake.forEach is not a function
document.querySelectorAll returns a NodeList, arguments inside a function is its own thing, and neither has the full array method set. Array.from(value) converts them.
6. The method does not exist on the class. Calling a missing method on a fresh instance produces a message with an unusual name in it:
class A {}
new A().go();
// TypeError: (intermediate value).go is not a function
(intermediate value) is how the engine refers to a value that was never assigned to a variable. It is not a variable called "intermediate value". It means the thing on the left of the dot was produced inline, here by new A(). Assign it to a name and the message will name it instead.
The one that looks like nothing is wrong
This is the cause that survives every proofread, because the line it blames is not the line that is broken.
const arr = [1, 2]
(function () {
console.log("iife")
})()
That is four lines with no obvious mistake. It throws:
TypeError: [1,2] is not a function
The message is the giveaway. It contains your array, being called.
JavaScript inserts semicolons automatically, but only where a line cannot continue. A line ending in ] followed by a line starting with ( reads perfectly well as a continuation, so no semicolon is inserted and the engine sees this instead:
const arr = [1, 2](function () { ... })()
[1, 2] followed by parentheses is a function call. The array is the function. Hence the message.
The same trap fires when a line ends in ] or ) and the next begins with (, [, `, +, -, / or *. Four defences, in order of how much they help:
- End statements with semicolons, or
- run a formatter such as Prettier, which inserts them consistently, or
- start any line that begins with
(or[with a leading semicolon, or - do not write code that starts a line with an opening bracket.
Blank lines change nothing here, which is why staring at the code rarely finds it. The parser does not care about blank lines.
The missing await
This is the most common cause in modern code, and it does not look like a typing mistake at all.
const getRows = async () => [1, 2, 3];
const rows = getRows();
rows.map((x) => x * 2);
// TypeError: rows.map is not a function
getRows returns an array, so rows.map should work. It does not, because an async function does not return its value. It returns a promise that will later resolve to that value. rows holds a Promise, and promises have no .map().
One word fixes it:
const rows = await getRows();
rows.map((x) => x * 2); // [2, 4, 6]
The tell is that typeof rows reports "object" rather than "undefined", and logging it prints Promise { <pending> }. Any time you see a pending promise where you expected data, you have found a missing await.
There is a near-identical version worth knowing, because it throws a different error:
const data = res.json(); // no await
data.items.map((x) => x);
// TypeError: Cannot read properties of undefined (reading 'map')
Here the promise itself is not being called, it is being read from. A promise has no items property, so data.items is undefined and the failure lands one step later as cannot read properties of undefined. Same root cause, different message, depending on whether the missing await sits before a call or before a property access.
The callback that never arrived
When a function expects to be handed another function and is not, the error names the parameter:
function Widget({ onSave }) {
return onSave();
}
Widget({});
// TypeError: onSave is not a function
Nothing is misspelled. The caller simply did not pass onSave. In component code this is the usual reason a handler blows up on click and nowhere else: the component is used in two places, and only one of them passes the prop.
Two fixes, and which one you want depends on whether the callback is required:
function Widget({ onSave = () => {} }) { ... } // optional, do nothing
function Widget({ onSave }) { // required, fail loudly
if (!onSave) throw new Error("Widget requires onSave");
...
}
A default of () => {} is right for genuinely optional hooks. It is wrong for a save button, where silence means the user's click did nothing and nobody found out.
What to check, in order
When the message appears and the cause is not obvious:
| Check | What it rules out |
|---|---|
typeof theValue |
Missing entirely, versus present but wrong type |
| Spelling and capitalisation | The single most common cause |
| Whether the value was reassigned | "It worked yesterday" |
| Log the whole imported module | Default versus named export shape |
Does it print Promise { <pending> }? |
A missing await |
| Look at the line above the error | Automatic semicolon insertion |
That last row is the one to remember. When the reported line looks correct, the mistake is usually on the line before it.
Preventing it
Most of these are caught before they run. A linter flags calls to methods that do not exist on a known type, and it is the standard defence against the semicolon trap too. TypeScript goes further and rejects n.toFixed(2) when n is typed as a string, which is cause 1 turned into a compile error.
The runtime version of the same defence is to narrow before calling, rather than hoping:
if (typeof maybe === "function") maybe();
That returns "guarded" instead of throwing, which is the right behaviour when a callback is genuinely optional. It is the wrong behaviour when the function is supposed to be there, for the same reason optional chaining can hide a typo: a guard around a bug turns a loud failure into a silent one.
Want to get these wrong somewhere safe? Start with the JavaScript track and break things in an editor that shows you the error immediately.
More from the blog

git cherry-pick: move one commit anywhere
Cherry-pick copies a commit onto your current branch. It creates a new commit with a new hash, so the original stays where it was.
Read more
CORS error: what the browser is actually blocking
A CORS error is the browser refusing a response the server already returned with a 200. Here is that proved with two real origins, and the fixes.
Read moreReady to write some code?
Put this into practice - start your first free lesson. No setup, no credit card.