Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. "x is not a function" in JavaScript: causes and fixes
Tools

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

By Max Arthur
Co-Founder & Content Marketer·August 30, 2026·8 Min read
A line of code with the value before the parentheses highlighted, showing it holds an array rather than a function

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.

Keep reading

More from the blog

One commit from a feature branch being copied onto main as a new commit with a different hash
September 5, 2026·8 min readTools

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
A successful 200 response arriving at the browser and being stopped before it reaches the page's JavaScript
September 3, 2026·7 min readTools

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 more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs MimoDevpuff vs Sololearn
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs freeCodeCamp
Read All Comparisons