Free Handbook · Runs in your browser

Errors & Debugging

The fifteen errors every JavaScript beginner hits, indexed with the exact message, why the engine said it and the fix; the console beyond log; the debugger and DevTools; reading library and framework errors; and the "works on my machine" checklist.

0 / 111 lessons🔥 0 day streak
ShareXLinkedIn

Module 11 · what you'll be able to do

  • Recognise the fifteen most common errors from their first line
  • Use console.table, group, time, trace and assert
  • Set a breakpoint, step, and inspect scope in DevTools or VS Code
  • Decode errors that come from React, Node, npm and the browser
  • Work through an environment difference methodically
01

The fifteen errors every beginner hits

Collected from the modules so far and from a decade of Stack Overflow. Each card is the real message, the reason, and the fix. Read them once now; you will recognise them for the rest of your career. The first four account for most of the total.

Error you will hit

1. TypeError: Cannot read properties of undefined (reading 'x')

javascript
const res = { data: null }
console.log(res.data.items.length)
Uncaught TypeError: Cannot read properties of null (reading 'items')
    at your code:2
Why the engine said that

The value before .items is null/undefined. The property named in the message is the one you tried to read; the thing that was missing is whatever came before it. Causes: data not loaded yet, a typo in an earlier key, an API that returned less than you expected, an array index past the end.

The fix

Log the object one level up. Guard with ?. when absence is legitimate; fix the upstream data when it is not.

Error you will hit

2. TypeError: x is not a function

javascript
const user = { name: "Ada" }
user.getName()
const items = "a,b"
items.map(x => x)
Uncaught TypeError: user.getName is not a function
    at your code:2
Why the engine said that

You called something that is not callable: a method that does not exist on that object (typo, wrong type — a string has no map), a value that is undefined because an import failed, or a property you forgot to invoke earlier so you got the function and then called its result.

The fix

Check typeof user.getName. Check the import (default vs named). Check the type of the receiver — Array.isArray(items).

Error you will hit

3. ReferenceError: x is not defined

javascript
function total(items) { return items.reduce((s, i) => s + i.price, 0) }
console.log(totl(cart))
Uncaught ReferenceError: totl is not defined
    at your code:2
Why the engine said that

No variable with that exact name exists in scope: a typo, a variable declared inside a block or function you are now outside of, a missing import, or a browser global used in Node (window, document).

The fix

Match the spelling and case. Move the declaration up a scope or pass the value in. Add the import.

Error you will hit

4. SyntaxError: Unexpected token

javascript
const config = {
  port: 3000
  host: "localhost",
}
SyntaxError: Unexpected token ':'   (or: missing ) after argument list / Unexpected end of input)
Why the engine said that

The file could not be parsed, so nothing ran. Missing comma, bracket or quote — usually one line above where the error points, because the parser only notices when it reads the next token.

The fix

Use an editor with bracket matching and a formatter; the red squiggle is on the wrong line, the fix is above it. Unexpected end of input = an unclosed { or (.

Error you will hit

5. TypeError: Assignment to constant variable

javascript
const count = 0
count++
Uncaught TypeError: Assignment to constant variable.
    at your code:2
Why the engine said that

You reassigned a const. Mutating an object's contents is fine; rebinding the name is not.

The fix

let if it changes. If you meant to change a property, you are not hitting this error.

Error you will hit

6. ReferenceError: Cannot access 'x' before initialization

javascript
console.log(api)
const api = createApi()
Uncaught ReferenceError: Cannot access 'api' before initialization
    at your code:1
Why the engine said that

A let/const used above its declaration — the temporal dead zone. Often: a const arrow function called before the line that defines it, or two modules importing each other (circular import).

The fix

Move the use below the declaration, or make it a function declaration (hoisted). For circular imports, move the shared piece to a third module.

Error you will hit

7. RangeError: Maximum call stack size exceeded

javascript
function getParent(node) { return getParent(node.parent) }
getParent(leaf)
Uncaught RangeError: Maximum call stack size exceeded
    at getParent (your code:1)
    at getParent (your code:1)
Why the engine said that

Infinite recursion — no base case, or a step that does not shrink the input. Also: a getter that reads itself (get x() { return this.x }), or an event handler that triggers its own event.

The fix

Add the base case first. For the getter, use a different backing name (#x).

Error you will hit

8. Uncaught (in promise) / UnhandledPromiseRejection

javascript
fetchUser(id).then(user => render(user))
// fetchUser rejects; no .catch, no await in a try
Uncaught (in promise) Error: HTTP 500
    at fetchUser (api.js:12)
Why the engine said that

A promise rejected and nothing handled it. In Node 15+ the process exits. The stack points at where it was thrown, not at the caller that forgot to catch.

The fix

Every promise gets an owner: await inside try/catch, or .catch(). Fire-and-forget calls get an explicit .catch(log).

Error you will hit

9. Promise { <pending> } / undefined from an async function

javascript
async function load() { return await api.get("/x") }
const data = load()
console.log(data.items)
undefined     // and console.log(data) shows Promise { <pending> }
Why the engine said that

Missing await: data is the promise. Also appears as "x.map is not a function" or "Cannot read properties of undefined" one line later.

The fix

const data = await load(), inside an async function or a module (top-level await).

Error you will hit

10. SyntaxError: Cannot use import statement outside a module

javascript
import fs from "node:fs"
SyntaxError: Cannot use import statement outside a module
Why the engine said that

Node treated the file as CommonJS, or the browser script tag lacks type="module".

The fix

"type": "module" in package.json / .mjs extension / <script type="module">.

Error you will hit

11. Error: Cannot find module './utils'

javascript
import { slug } from "./utils"
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/src/utils' imported from /app/src/index.js
Why the engine said that

ESM needs the extension (./utils.js); the path is relative to the importing file, not the project root; or the package is not installed (npm install). On Linux servers, file names are case-sensitive — Utils.jsutils.js, which works on a Mac and breaks in CI.

The fix

Add .js. Check the relative path from this file. Match the case exactly.

Error you will hit

12. CORS: No 'Access-Control-Allow-Origin' header is present

javascript
fetch("https://api.other.com/data")   // from https://myapp.com
Access to fetch at 'https://api.other.com/data' from origin 'https://myapp.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Why the engine said that

The browser blocks reading cross-origin responses unless the server opts in. Your code is fine; the other server did not send the header. Nothing you do client-side can fix it — that is the point.

The fix

Own the API? Add CORS headers (or the cors middleware). Don't? Call it from your own backend and proxy the result.

Error you will hit

13. [1, 2, 10].sort() → [1, 10, 2] and 0.1 + 0.2 !== 0.3

javascript
console.log([1, 2, 10].sort())
console.log(0.1 + 0.2 === 0.3, (0.1 + 0.2).toFixed(2))
[ 1, 10, 2 ]
false 0.30
Why the engine said that

Not errors — wrong answers. Default sort compares strings; binary floats cannot represent 0.1.

The fix

sort((a, b) => a - b). Compare floats with a tolerance; do money in integer cents.

Error you will hit

14. TypeError: Converting circular structure to JSON

javascript
const a = { name: "a" }
a.self = a
JSON.stringify(a)
Uncaught TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    --- property 'self' closes the circle
Why the engine said that

The object refers to itself (directly or via a parent/child link — DOM nodes, ORM models, Express req). JSON has no way to express that.

The fix

Stringify a plain view of the data: pick the fields you need, or pass a replacer function. Never stringify framework objects.

Error you will hit

15. Warning: Each child in a list should have a unique "key" prop / Hooks called conditionally

javascript
// React
items.map(item => <li>{item.name}</li>)
if (open) { const [x, setX] = useState() }
Warning: Each child in a list should have a unique "key" prop.
Error: Rendered more hooks than during the previous render.
Why the engine said that

Framework rules, not language rules: React identifies list items by key, and hooks must run in the same order every render. Every framework has a handful of these — read its "rules" page once.

The fix

<li key={item.id}>. Call hooks at the top level, unconditionally; put the condition inside.

The meta-rule
Read the first line of the error twice. Copy it into a search engine without your variable names. The top result is almost always the answer, because every one of these has been hit a million times.
02

console beyond log

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
[
  { id: 1, name: 'Ada', role: 'admin' },
  { id: 2, name: 'Bob', role: 'dev' }
]
Request 42
method: GET
status: 200
loop: 0ms
Assertion failed: first user should be dev { id: 1, name: 'Ada', role: 'admin' }
rendered: 1
rendered: 2
{ status: 200, retries: 3 }
something failed
{ nested: { deep: { deeper: 1 } } }

This sandbox renders table as an array and time as 0 ms; in DevTools and Node you get a real grid and real timings. console.trace() prints the current call stack — the fastest way to answer "who called this?".

console.log of an object is live
DevTools shows the object's state when you expand it, not when you logged it. If it mutates later you see the later value. Log structuredClone(obj) or JSON.stringify(obj) to freeze a snapshot.
03

The debugger and DevTools

A breakpoint pauses the program at a line so you can inspect every variable in scope, step line by line, and watch values change — the thing console.log approximates badly. Browser: DevTools → Sources → click a line number, or write debugger in the code. Node: node --inspect-brk app.js and open chrome://inspect, or use VS Code's Run and Debug (F5) with a launch.json.

javascriptdebug.js
function reconcile(orders, payments) {
  const byId = new Map(payments.map(p => [p.orderId, p]))
  const unpaid = []
  for (const order of orders) {
    const payment = byId.get(order.id)
    debugger                                   // pauses here when DevTools is open
    if (!payment || payment.amount < order.total) unpaid.push(order)
  }
  return unpaid
}

// While paused, in the console you can run any expression in the current scope:
//   byId.size          → how many payments matched
//   order              → the current order
//   payment?.amount    → is it undefined? that is your bug
// Step over (F10) runs the line; step into (F11) enters a call; resume (F8) continues.
// A CONDITIONAL breakpoint (right-click the line) pauses only when e.g. `order.id === 42`.
// A LOGPOINT prints without pausing — console.log you do not have to remove.
  1. 1
    Reproduce

    Find the smallest input that fails. If you cannot reproduce it, you cannot know you fixed it.

  2. 2
    Locate

    Read the stack trace to the first frame in your code. Put a breakpoint there or one frame above.

  3. 3
    Inspect

    When paused: Scope panel for every variable, Watch for expressions, Call Stack to jump to callers. Hover a variable in the source to see its value.

  4. 4
    Hypothesise and test

    "payment is undefined because orderId is a string but order.id is a number." Test it in the console: typeof payments[0].orderId. Then fix it. Then write the test that would have caught it.

DevTools panelUse it for
ConsoleErrors, logs, running expressions against the live page
SourcesBreakpoints, stepping, watch expressions, pretty-print minified code
NetworkEvery request: status, timing, headers, request/response bodies. The first place to look for a failing fetch.
ElementsThe live DOM and computed CSS; edit in place; break on DOM changes
ApplicationlocalStorage, cookies, service workers, cache
PerformanceFlame chart of what the main thread was doing; find long tasks
04

Reading library and framework errors

Most errors you meet after week one come from code you did not write: the top of the stack is inside node_modules. The skill is to find your frame and the contract you broke. Libraries increasingly include the fix in the message — read the whole thing, including the link.

MessageSourceWhat it usually means
ERR_REQUIRE_ESM / require() of ES Module not supportedNodeYou required an ESM-only package. Use import, or an older version.
EADDRINUSE: address already in use :::3000NodeAnother process has the port — usually your own last run. lsof -i :3000 and kill it.
ECONNREFUSED 127.0.0.1:5432Node / pgNothing is listening: the database is not running, or the host/port in your env is wrong.
npm ERR! ERESOLVE unable to resolve dependency treenpmTwo packages want incompatible versions of a third. Read which two; upgrade one, or --legacy-peer-deps as a last resort.
Hydration failed because the initial UI does not matchReact / NextServer HTML ≠ client render: Date.now(), window checks or random values during render. Make render deterministic.
Objects are not valid as a React childReactYou put an object in JSX: {user} instead of {user.name}.
Failed to fetch / NetworkErrorBrowserCORS, offline, mixed content (http from https), or the server closed the connection. Check the Network tab — the console message is deliberately vague.
Unexpected token < in JSON at position 0BrowserYou parsed HTML as JSON: the API returned an error page or your dev server returned index.html for an unknown route. Log res.status and await res.text().
A habit worth building
When a library error costs you more than ten minutes, write down the message and the fix in a project TROUBLESHOOTING.md. Teams that do this stop re-solving the same six problems.
05

"It works on my machine"

The code is the same; the environment is not. JavaScript has more environments than most languages — Node versions, browsers, bundlers, OS file systems, time zones — so this happens often. Work down the list; it is almost always one of these.

  1. Node version. node --version here and there. Pin it in package.json engines and .nvmrc; use the same major in CI and Docker.
  2. Dependencies. Did they run npm ci (lockfile) or npm install (drift)? Is package-lock.json committed? Same package manager?
  3. Environment variables. .env is not committed (good) — so it is missing on the server. process.env.X is undefined, and undefined becomes the string "undefined" in a URL.
  4. Case-sensitive paths. import "./Utils" works on macOS and Windows, fails on Linux (CI, Docker, servers).
  5. Time zone and locale. new Date("2026-09-20").getDate(), toLocaleString(), sort order of strings — all depend on the machine. Servers run in UTC.
  6. Browser. Which browser, which version, extensions (ad blockers break fetch), private mode (localStorage throws), mobile Safari (many differences).
  7. Build vs dev. Vite/Next dev mode is not the production bundle: tree-shaking, minification, NODE_ENV, and hydration differ. Run the production build locally before saying it works.
  8. Data. Your local database has three rows; production has three million and one with a null in a column you assumed was set.
javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
node: 22.11.0
tz: UTC  locale: en-US
NODE_ENV: set (10 chars)
DATABASE_URL: EMPTY
API_BASE: set (23 chars)
undefined/users
Mid-levelA feature works locally but fails in production. Walk me through how you would debug it.

First reproduce against production-like conditions: production build, same Node version, same env vars shape. Then get the actual error — logs, error tracker, the Network tab — not a description of it. Compare the environments systematically: versions, dependencies (lockfile), env vars, case-sensitive paths, time zone, data shape. Bisect if needed: what changed between the last working deploy and this one? Fix, add a test or a startup check that would have caught it, and write down the cause.

What they are really testing: Method over guesswork, and whether you know the usual suspects.

Finish the JavaScript handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.