forEach is not a function means the value you called it on isn’t an array; confirm the type, then reshape the data or use the right iterator.
You’ll see this error when JavaScript runs .forEach() on a value that doesn’t have that method. Most of the time it’s a plain object, a string, a number, null, undefined, or an “array-like” value that looks close to an array but isn’t one.
The fix isn’t “sprinkle a check and move on.” The clean fix is to learn what your data really is at runtime, then make the data match the operation. Once you do that, the crash stops, and your code gets easier to trust when inputs change.
Why This Error Happens In Plain Terms
.forEach() lives on Array.prototype. That means only real arrays can call it directly. If your variable is an object like { items: [...] } or a value like null, there’s no forEach function to call.
“Is not a function” is JavaScript’s blunt way of saying you tried to call something that either doesn’t exist or isn’t callable. In this case, the property name is forEach, so the runtime is telling you your value is not an array (or not array-shaped enough to act like one).
Quick Checks That Tell You The Truth
- Log the value — Print the variable right before the failing line, then expand it in DevTools so you see the real shape.
- Check Array.isArray — Run
Array.isArray(value)so you don’t guess based on how it “looks” in a console preview. - Check the path — If the code is
data.items.forEach(...), logdata, thendata.items, then the final variable you iterate. - Confirm the timing — If data loads async, log on the first render and after the fetch resolves, since the value can change between those moments.
forEach Is Not A Function On Real Data Shapes
This error shows up most when data changes shape between “works in my test” and “real payload in production.” A response that is an array in one case can be an object in another case. A field you thought always existed may arrive as null. A DOM query may return a collection you can loop over, yet it still isn’t an array.
The table below matches common shapes to fixes that hold up. Use it to spot the pattern fast, then use the next sections to apply the right fix without hiding bugs.
| What You Have | Why It Breaks | Fix That Holds Up |
|---|---|---|
Plain object ({...}) |
Objects don’t have .forEach() |
Use Object.values() or Object.entries() |
| String | Strings iterate by characters, not items | Split to an array, or use for...of |
| NodeList / HTMLCollection | Array-like, not always an array | Convert with Array.from() or spread |
null / undefined |
Nothing to iterate | Default to [] or guard with optional chaining |
| One item, not a list | You’re treating one thing like many | Wrap in an array, or branch logic |
If you copied the error text straight from your console, you may also see it written with caps as forEach Is Not A Function. That’s the same problem: you called .forEach() on a non-array value.
Fast Debug Steps That Find The Root Cause
Stopping the crash is easy. Finding why it crashes is what saves you next week when a user clears a field, an API returns a different payload, or a refactor changes a property name.
Put The Log In The Right Spot
Log the value on the line right before it fails. Logging it “somewhere above” can mislead you when the value changes between renders or between async steps.
- Log right above the call — Print the exact variable that receives
.forEach(), not a parent object. - Log the full chain — If you do
payload.data.items, log each part until you see which piece turns intoundefinedor an object. - Log a sample item — After conversion, print
items[0]so you confirm you’re iterating the intended data.
Stop The Crash While You Inspect
These guards keep the page alive during debugging. Use them while you trace the source, then decide whether to keep the guard as a real contract or replace it with stricter validation.
- Guard with Array.isArray —
Array.isArray(items) && items.forEach(...)prevents the call when it’s not a list. - Default to an empty array —
const items = payload.items ?? []makes “no items” behave like an empty list. - Fail fast with a clear error — Throw a custom error when a value must be an array so you catch the bug close to the source.
Two Tiny Tests That Catch Most Cases
- Test the empty case — Simulate the API returning zero items and confirm your UI still renders without calling
.forEach()onnull. - Test the error payload — Simulate a response that returns an error object and confirm your code branches before iterating.
Fix Patterns For The Most Common Causes
Once you know what you have, the fix is usually a small reshape. The goal is to make your data match your intent, not to force a conversion that masks a real mistake.
When You Actually Have An Object
If you see keys and values, you have an object. Pick the method that matches what you need: values only, keys only, or both.
- Loop over values — Use
Object.values(obj).forEach(v => ...)when keys don’t matter. - Loop over entries — Use
Object.entries(obj).forEach(([k, v]) => ...)when you need the key and the value. - Keep ordering explicit — If order matters, sort entries first so the output stays predictable across inputs.
If you expected an array but received an object, pause and verify the upstream source. Many APIs wrap lists inside an object like { data: [...] }, which means you want response.data, not response.
When The Value Can Be Null Or Missing
Forms, storage reads, and network calls often return “nothing” for a field. That’s normal. The trick is to define what “nothing” means in your code and stick to it.
- Set a safe default —
const items = response.items ?? []makes the rest of your code simpler. - Use optional chaining —
response.items?.forEach(...)runs only when the list exists. - Normalize at one entry point — Convert missing values to
[]in one place, then assume arrays everywhere else.
If you’re working with TypeScript, consider typing the value as Item[] and keeping a single conversion step at the boundary, right after parsing or fetch.
When You Have A NodeList Or HTMLCollection
document.querySelectorAll() returns a NodeList. Many modern browsers allow nodes.forEach, yet relying on that can still bite you in older runtimes and some test setups. Converting once keeps behavior consistent.
- Convert with Array.from —
Array.from(nodes).forEach(node => ...)is clear and reliable. - Convert with spread —
[...nodes].forEach(node => ...)reads clean and works well for UI lists. - Use for…of —
for (const node of nodes) { ... }avoids conversion when you don’t need array methods.
When The Value Is Array-Like But Not An Array
Some APIs and older browser calls return “array-like” objects with a length but no array methods. Treat them as raw inputs, convert once, then keep arrays inside your core logic.
- Convert at the boundary — Use
Array.from(arrayLike)once, right where the value enters your code. - Avoid double conversion — Don’t wrap the same value in multiple layers of
Array.fromacross functions. - Confirm elements after conversion — Log the first element so you know you’re iterating the right item type.
When You’re Iterating The Wrong Property
This is a sneaky one: the list exists, but you’re pointing at the wrong level in the object. It happens a lot with HTTP libraries and nested payloads.
- Check the wrapper keys — Look for
data,items,results, orpayloadwrappers that hide the array one level deeper. - Rename for clarity — Assign
const items = response.data.itemsso you don’t keep repeating a long chain and risk a typo. - Guard nested reads — Use
response?.data?.items ?? []when the wrapper might be missing.
Common Places Where This Pops Up
When you see the same crash in different spots, it can feel random. It’s usually the same “shape mismatch” arriving through different doors.
API Responses That Flip Between List And Message
Some endpoints return an array when there are results, then return an object with a message when there are none. That’s when foreach is not a function shows up the moment your code hits an empty state.
- Log the full response — Print the entire JSON once so you catch alternate payloads.
- Normalize after parsing — Convert missing or message states into
[]so UI code can always iterate safely. - Branch on error payloads — If the response is an error object, return early before any iteration happens.
Form State That Changes Type Mid-Edit
Tags, multi-selects, and checkbox groups often store values as arrays, then briefly store an empty string or null while the user clears a field. If your code iterates during that moment, you’ll get a crash.
- Store one internal type — Keep state as an array at all times, even when empty.
- Coerce input on change — Convert a single value into
[value]when the rest of the code expects a list. - Use a safe initial value — Initialize to
[]so the first render never tries to iterateundefined.
Async Data That Isn’t Ready Yet
A component can render before data arrives. If the first render sees undefined, then the fetch fills it later, the first pass can still crash.
- Initialize arrays — Start state as
[]when you intend to iterate. - Render an empty state — If the list is empty, show a small message, then render items when they arrive.
- Prepare data right after fetch — Map, filter, and normalize as soon as you parse JSON, then pass clean arrays into the UI.
JSON Parsing And Storage Reads
Local storage and file reads are common sources of “surprise shapes.” A stored value can be a string, or JSON can be valid yet not the type you expect.
- Parse with a fallback — If parsing fails, fall back to
[]so iteration stays safe. - Validate the parsed type — After parsing, confirm
Array.isArray(parsed)before you call.forEach(). - Handle single-item storage — If you sometimes store one item, wrap it into an array when you read it back.
Prevent It From Returning
After you fix the crash, a few habits keep it from coming back when someone changes an API, refactors state, or adds a new data source.
Normalize Data At The Boundaries
Boundary points are where data enters your code: network responses, form events, storage reads, and DOM queries. Normalize there, then keep internal code simple.
- Write a shape contract — Decide what each function expects: array, object, string, or nullable, then keep it consistent.
- Convert once — Turn “array-like” into arrays at the edge, then pass arrays through the rest of the app.
- Keep defaults consistent — Use
[]as the “no items” value across the project so checks stay easy to read.
Use Guards That Don’t Hide Mistakes
Guards are useful, but they can also mask a broken contract. Use them with intent.
- Guard at the boundary — If a response field can be missing, normalize it right away, not scattered across the codebase.
- Throw in core logic — If a function truly requires an array, throw an error when it gets something else.
- Add a small test set — Include test inputs for empty list, missing field, and error object so the contract stays real.
Pick The Right Iterator When You Don’t Need forEach
.forEach() is fine for side effects, yet it’s not always the best tool. If you want a transformed list, use .map(). If you want a filtered list, use .filter(). If you want one value, use .reduce() or a loop with a break.
- Use map for transforms — It returns a new array, which is easier to reason about than pushing into external state.
- Use filter for conditions — It keeps intent visible and removes the need for manual branching inside a loop.
- Use for…of for early exits — You can
breakandcontinue, which you can’t do inside.forEach().
If you still see forEach Is Not A Function after applying these patterns, the value is changing between your check and your use. Put the log directly above the failing line, confirm the shape at that exact moment, and follow the data back to the first place it shifts.
