Ch. 1 · JavaScript

TypeError: x Is Not a Function in JavaScript: Causes and Fixes

Fix TypeError: x is not a function in JavaScript: wrong imports, shadowed names, lost this, a missing semicolon or .map on a non-array.

~7 min readbeginnerupdated Oct 4, 2026

You called something with parentheses, and V8 (Chrome, Edge, Node.js) refused:

TypeError: data.map is not a function
Text

The text before is not a function is the exact expression you tried to call. It exists (otherwise you would get a ReferenceError or Cannot read properties of undefined), but its value is a number, string, plain object, undefined or something else that cannot be invoked. Your job is to find out what it really holds and why.

Firefox uses nearly the same wording (TypeError: data.map is not a function). Safari phrases it as TypeError: data.map is not a function. (In 'data.map(...)', 'data.map' is undefined), which also tells you the value. Bundled code can produce odd names such as (0 , _utils.default) is not a function, which is the compiled form of calling a default import.

Quick fix checklist

  • Log typeof x and x itself right before the call. If it is 'object', see what keys it has.
  • If x came from an import, check default versus named: import x from versus import { x } from.
  • If the message says .map, .filter or .forEach, check Array.isArray(value); you probably have an object wrapping the array, a string, or a NodeList.
  • Search the enclosing scope for another variable or parameter with the same name.
  • If it says this.something, check how the method was passed: as a callback it may run with a different this.
  • If it says (intermediate value)(...) or someCall(...) is not a function, look for a missing semicolon on the line above.

Before you start

You should know the difference between referencing a function (save) and calling it (save()), how ES module imports work at a basic level, and that functions are ordinary values that can be stored in variables, passed around and overwritten. That last fact is why this error exists: nothing stops a name that used to hold a function from later holding something else.

Why it happens

Calling x() asks the engine to run the internal [[Call]] behaviour of whatever x evaluates to. Only functions (including classes, methods and bound functions) have it. Everything else throws. The common ways a non-function ends up there:

const response = { items: [1, 2, 3] };
try { response.map((n) => n * 2); } catch (e) { console.log(String(e)); }

const counter = { count: 3 };
try { counter.count(); } catch (e) { console.log(String(e)); }

function print(items, format = 'short') {
  return items.map((item) => format(item)); // parameter shadows a format() function
}
try { print(['a']); } catch (e) { console.log(String(e)); }

try { const m = new Map(); m.map((x) => x); } catch (e) { console.log(String(e)); }
// TypeError: response.map is not a function
// TypeError: counter.count is not a function
// TypeError: format is not a function
// TypeError: m.map is not a function
JavaScript
  • Calling a property that is a value. counter.count is a number. This often happens after a refactor turns a method into a getter or a field.
  • Shadowing. The format parameter hides any outer format function inside print, and its default is a string.
  • Array methods on non-arrays. Map, Set, strings, plain objects and the DOM’s NodeList from querySelectorAll do not have .map. Convert with Array.from(value) or [...value] first.

Two causes deserve their own explanation: imports and semicolons.

Wrong import shape. Importing a module’s default export when it only has named exports fails at link time in native ESM, with a clear SyntaxError: The requested module './price.mjs' does not provide an export named 'default'. But through bundlers, TypeScript’s interop or CommonJS modules, the mismatch survives until the call:

import formatPrice from './format.cjs'; // format.cjs: exports.formatPrice = (cents) => ...
console.log(typeof formatPrice, Object.keys(formatPrice));
try { formatPrice(1999); } catch (e) { console.log(String(e)); }
// object [ 'formatPrice' ]
// TypeError: formatPrice is not a function
JavaScript

When Node imports a CommonJS file, the default import is the whole module.exports object. The function lives at formatPrice.formatPrice, so the fix is import { formatPrice } from './format.cjs'. The same mistake happens with namespace imports (import * as price then price()) and with dynamic import(), which always resolves to the module namespace object rather than its default.

Missing semicolon before an IIFE. Automatic semicolon insertion does not add a semicolon before a line that starts with ( or [, so the two lines join:

const greet = function () { return 'hi' }
(function () { console.log('setup ran') })()
JavaScript
(function () { console.log('setup ran') })()
                                          ^

TypeError: (intermediate value)(...) is not a function
Text

The engine read function () {...}(function () {...})(): it called the first function with the second as an argument, got the string 'hi', then tried to call 'hi'(). Note that setup ran never prints. A line starting with [ causes the equivalent property-access bug.

Step-by-step walkthrough

Step 1: Reproduce and copy the exact name

The message tells you precisely which expression was called. response.data.items.map is not a function is far more specific than a vague “something is not a function”: you know response.data.items exists and that its map property is not callable.

Step 2: Inspect the value at the call site

Put a log or breakpoint immediately before the call:

const value = { data: [{ id: 1 }], next: null };
console.log(typeof value, Array.isArray(value), Object.keys(value));
// object false [ 'data', 'next' ]
JavaScript

This one line usually answers the question. typeof of 'undefined' means the property is missing; 'object' with unexpected keys means you have a wrapper; 'string' means data was never parsed.

Step 3: Trace where the value came from

Follow the variable back. Is it a parameter? Check every caller. An import? Open the module and see what it actually exports. A this property inside a callback? Check what the callback is passed to. In this class, the method works when called directly but fails as a timer callback:

class Poller {
  constructor() { this.ticks = 0; }
  render() { console.log('tick', this.ticks); }
  tick() { this.ticks += 1; this.render(); }
  start() { setTimeout(this.tick, 0); }
}
process.on('uncaughtException', (e) => console.log(String(e)));
new Poller().start();
// TypeError: this.render is not a function
JavaScript

In Node, a setTimeout callback runs with this set to the Timeout object; in browsers it is window. Neither has render. Passing () => this.tick() or this.tick.bind(this), or declaring tick = () => {...} as a class field, keeps this pointing at the instance.

Step 4: Fix the source, then make it obvious

Change the import, rename the shadowing variable, normalise the data or bind the method. Then make wrong shapes fail early: validate API responses where they enter the app, and turn on ESLint’s no-shadow and TypeScript, which flags most of these cases before you run anything.

Worked scenario

An order history page has worked for months. After the backend team adds pagination, it crashes:

async function loadOrders() {
  // Before: the API returned [ ...orders ]
  // Now:    the API returns { data: [ ...orders ], next: '/orders?page=2' }
  return { data: [{ id: 1, total: 4200 }, { id: 2, total: 1999 }], next: '/orders?page=2' };
}

const orders = await loadOrders();
try {
  console.log(orders.map((o) => o.id));
} catch (e) {
  console.log(String(e));
}
// TypeError: orders.map is not a function
JavaScript

Diagnosis. orders exists, so it is not a missing variable. Logging Object.keys(orders) shows [ 'data', 'next' ]: the array is now one level down. The UI code assumed the old contract.

Fix. Normalise at the boundary so the rest of the app keeps receiving an array, and fail loudly if the shape changes again:

async function loadOrders() {
  return { data: [{ id: 1, total: 4200 }, { id: 2, total: 1999 }], next: '/orders?page=2' };
}

async function fetchOrderPage() {
  const body = await loadOrders();
  if (!Array.isArray(body.data)) {
    throw new TypeError(`Expected body.data to be an array, got ${typeof body.data}`);
  }
  return { orders: body.data, next: body.next };
}

const { orders } = await fetchOrderPage();
console.log(orders.map((o) => o.id));
// [ 1, 2 ]
JavaScript

Common mistake

The tempting fix is a defensive check at the call: if (typeof onSave === 'function') onSave(), or (orders || []).map(...). It silences the error, but now the save button does nothing, or the list is empty, and nobody knows why. That guard is correct only for genuinely optional callbacks (options.onProgress?.() is a fine idiom). For required functions and data, a silent no-op is worse than a crash.

Another trap is “fixing” a CommonJS import with formatPrice.default || formatPrice. That works by accident and breaks the next time the module’s build output changes. Look at what the module exports and import that name.

Verify the behavior

Test the normalising function with the current shape, the old shape and a broken one:

import assert from 'node:assert/strict';

function normaliseOrders(body) {
  if (!Array.isArray(body?.data)) {
    throw new TypeError(`Expected body.data to be an array, got ${typeof body?.data}`);
  }
  return body.data;
}

assert.deepEqual(normaliseOrders({ data: [{ id: 1 }], next: null }).map((o) => o.id), [1]);
assert.throws(() => normaliseOrders([{ id: 1 }]), /got undefined/);
assert.throws(() => normaliseOrders({ data: 'oops' }), /got string/);
console.log('order normalisation verified');
// order normalisation verified
JavaScript

For import problems, run console.log(Object.keys(await import('./module.js'))) once: it prints exactly which names the module provides, including default.

Interview exercise

loadConfig returns a plain object. Why does this code throw TypeError: loadConfig(...) is not a function, and how would you prevent the whole class of bug?

const config = loadConfig()
(async () => { await start(config) })()
JavaScript

Answer and reasoning

There is no semicolon after loadConfig(), and the next line starts with (, so automatic semicolon insertion does not apply. The engine parses loadConfig()(async () => {...})(): it tries to call the object that loadConfig() returned, passing the arrow function as its argument. V8 names the callee loadConfig(...) because the thing being called is the result of that call; in the IIFE example above it was an anonymous function expression, so V8 printed (intermediate value)(...) instead. The start code never runs. To prevent it, either use semicolons consistently or, in a no-semicolon style, start any line beginning with (, [ or a template literal with a leading ;. Prettier (which inserts those defensive semicolons in no-semicolon mode) and ESLint’s no-unexpected-multiline rule handle this automatically, which is better than relying on memory.

Continue learning

Keep practising with the JavaScript interview questions and the JavaScript MCQs. The lost-this case is explained fully in this keyword binding rules, and the import cases make more sense after module live bindings. If the value turned out to be undefined rather than the wrong type, see Cannot read properties of undefined, and for ESM versus CommonJS errors see Cannot use import statement outside a module. MDN’s page is TypeError: “x” is not a function.

More in JavaScript

esc