Developer Debugging Guides2026-06-04·16 min read

Coding Errors: 8 Types and How to Fix Each One

Learn to identify and fix the most common coding errors — syntax, runtime, and logic errors — with a practical debugging workflow and real code examples.

#debugging#error-fix#javascript#typescript#troubleshooting

FlowQL Team

AI Search Optimization Experts

You pasted the error into your AI chat tool. It gave you a fix. You applied it. The same error came back — or a new one showed up in its place. Sound familiar?

AI tools are genuinely good at the routine 80% of coding. But when errors get tangled in state, environment, or subtle logic, the same tools that generated your code start generating noise. Understanding how to read, classify, and systematically debug programming errors is the skill that separates developers who get unstuck quickly from developers who spin for hours.

This guide covers the three types of coding errors, how to read error messages that actually tell you what's wrong, walkthroughs of the five most common errors you'll hit, and a debugging workflow that works whether you're in a vanilla JS project or a complex TypeScript monorepo.

What Are Coding Errors?

A coding error is any defect in your source code that causes it to behave differently from what you intended — ranging from code that refuses to run at all, to code that runs but silently produces wrong results. Programming errors are not bugs introduced by cosmic rays. They're caused by incorrect syntax, wrong assumptions about data, or faulty logic, and all three have reliable patterns to identify and fix.

The Mozilla Developer Network error reference documents every standard JavaScript error type with causes and examples — bookmark it now if you haven't already.

The Three Types of Coding Errors

Every coding error you'll encounter falls into one of three categories. Knowing which type you're dealing with tells you where to look and how to fix it.

Syntax Errors

A syntax error means your code violates the grammar rules of the programming language. The parser can't even begin to execute it. These are the easiest errors to fix because the engine tells you exactly where it choked.

// BEFORE — syntax error: missing closing parenthesis
function greetUser(name {
  return `Hello, ${name}`;
}
// SyntaxError: Unexpected token '{'

// AFTER — fixed
function greetUser(name) {
  return `Hello, ${name}`;
}

Syntax errors are caught before runtime. Your editor's linter (ESLint, the TypeScript compiler, Python's interpreter) will highlight them before you even run the code. If you're seeing syntax errors frequently, configure your editor to show linting inline — catching them at write-time beats catching them at run-time every time.

Common triggers: missing brackets or parentheses, unclosed string literals, invalid use of reserved words, and JSON with trailing commas. TypeScript adds type annotation syntax on top of this, so type errors during compilation are also in this family.

Runtime Errors

A runtime error occurs when syntactically valid code tries to do something impossible during execution. The code runs, hits a wall, and throws an exception.

// BEFORE — runtime error: calling a method on undefined
const user = null;
const name = user.name; // TypeError: Cannot read properties of null (reading 'name')

// AFTER — guard before access
const user = null;
const name = user?.name ?? "Guest"; // Returns "Guest" safely

Runtime errors are trickier than syntax errors because they depend on data and program state. The same code can work fine in development (where user is always populated) and crash in production (where an API returns null). The stack trace is your primary tool here — it shows you exactly which line threw and the chain of calls that got you there.

Logic Errors

Logic errors are the most dangerous type. The code runs without throwing any error, but produces wrong output. There's no crash, no red text — just incorrect behavior.

// BEFORE — logic error: wrong operator causes silent failure
function isAdult(age) {
  return age = 18; // Assignment, not comparison! Always returns 18 (truthy)
}

console.log(isAdult(15)); // Logs 18 instead of false

// AFTER — use strict equality
function isAdult(age) {
  return age >= 18; // Correct comparison
}

console.log(isAdult(15)); // Logs false

Logic errors require you to form a hypothesis about what the code should do and then trace through what it actually does. They're often subtle: off-by-one errors, wrong variable scope, incorrect order of operations, or misunderstanding what a library function returns.

| Error Type | When It Appears | How to Find It | Example | |------------|----------------|----------------|---------| | Syntax | Before execution | Linter / compiler | if (x = 5 missing ) | | Runtime | During execution | Stack trace | null.property | | Logic | During execution (no crash) | Testing / logging | Wrong calculation |

How to Read an Error Message

The first thing developers do wrong when they hit an error is scroll past the message to start Googling. Don't. Read the entire error first — it almost always tells you what went wrong and where.

A JavaScript error message has three parts worth your attention:

1. The error typeTypeError, ReferenceError, SyntaxError, RangeError. The type tells you the category of failure. A ReferenceError means you used a variable that doesn't exist. A TypeError means you used a value as the wrong type (calling a non-function, accessing a property on null).

2. The message — The human-readable description. Cannot read properties of undefined (reading 'map') tells you exactly what property you tried to access and that the object was undefined. This message alone often tells you where to look.

3. The stack trace — The call chain from where the error was thrown back to the entry point of your program. Read it top to bottom. The first line in your code (not a library) is almost always where you should look first.

TypeError: Cannot read properties of undefined (reading 'map')
    at ProductList (ProductList.jsx:12:18)     ← your code, start here
    at renderWithHooks (react-dom.development.js:14985:18)
    at mountIndeterminateComponent (react-dom.development.js:17811:13)
    at beginWork (react-dom.development.js:19049:16)

In this stack trace, ProductList.jsx:12:18 means line 12, column 18 in your file. Open it directly. Don't read the React internals below it — they're the framework doing its job.

The Node.js error documentation covers Node-specific error codes (ENOENT, EACCES, ECONNREFUSED) that look cryptic but map directly to OS-level failures. Keep it handy for backend errors.

5 Most Common Coding Errors (and How to Fix Them)

"undefined is not a function"

This is the most common runtime error in JavaScript. It means you called something as a function that is not actually a function — usually because the variable is undefined, or because you misspelled a method name.

// BEFORE — calling a method that doesn't exist on the object
const items = ["a", "b", "c"];
items.forEach(item => console.log(item)); // Works fine

const data = { items: ["a", "b", "c"] };
data.forEach(item => console.log(item)); // TypeError: data.forEach is not a function
// data is a plain object, not an array — objects don't have forEach

// AFTER — access the correct property first
const data = { items: ["a", "b", "c"] };
data.items.forEach(item => console.log(item)); // Works correctly

Before calling any method, verify two things: (1) the object is the type you think it is (typeof, Array.isArray()), and (2) the method name is spelled correctly. A quick console.log(typeof data) before the call saves a lot of head-scratching.

"Cannot read property of null"

This error means you tried to access a property or call a method on a value that is null or undefined. JavaScript can't look up properties on nothing.

// BEFORE — no null check before property access
async function loadUser(id) {
  const user = await db.users.findOne({ id }); // Returns null if not found
  return user.email; // TypeError: Cannot read properties of null (reading 'email')
}

// AFTER — handle null explicitly
async function loadUser(id) {
  const user = await db.users.findOne({ id });
  if (!user) {
    throw new Error(`User ${id} not found`);
  }
  return user.email;
}

// OR use optional chaining for non-critical paths
async function getUserEmail(id) {
  const user = await db.users.findOne({ id });
  return user?.email ?? null; // Returns null if user is null/undefined
}

Optional chaining (?.) and nullish coalescing (??) were added in ES2020 specifically to handle this pattern. The MDN optional chaining documentation shows every variation. For database queries and API responses, always account for the empty/null case — it will happen in production.

For a deeper dive into this class of error in Next.js specifically, the NextJS hydration guide covers how null state mismatches between server and client produce hydration errors that are harder to trace.

"Unexpected token"

An unexpected token error is a syntax error. The parser hit a character or keyword it didn't expect given what came before. The fix is almost always a typo or a missing delimiter.

// BEFORE — missing comma in object literal
const config = {
  host: "localhost"
  port: 3000  // SyntaxError: Unexpected identifier 'port'
};

// AFTER — add the missing comma
const config = {
  host: "localhost",
  port: 3000
};
// BEFORE — trailing comma in JSON (valid JS, invalid JSON)
{
  "name": "my-app",
  "version": "1.0.0",
}

// AFTER — remove the trailing comma
{
  "name": "my-app",
  "version": "1.0.0"
}

When you see "unexpected token," look at the line the error references and the line before it. The problem is almost always at the boundary between the two. Also check for mismatched quotes, unclosed template literals (backtick strings), and JSX with un-escaped { characters in text content.

"Module not found"

This error means the import resolver couldn't locate the file or package you're trying to import. The most common causes: the package isn't installed, the path is wrong, or there's a misconfigured alias.

// BEFORE — importing a package that isn't installed
import { format } from "date-fns";
// Error: Cannot find module 'date-fns' or its corresponding type declarations

// FIX: install it
// npm install date-fns

// BEFORE — wrong relative path
import { Button } from "../components/Button"; // Assumes Button.tsx is one level up
// Error: Cannot find module '../components/Button'

// AFTER — check the actual path
import { Button } from "../../components/Button"; // Correct depth

For TypeScript projects, "Module not found" can also mean the types package is missing. If you installed canvas but see TypeScript errors, you also need npm install --save-dev @types/canvas. The npm peer dependency conflict fix guide covers the messier variant of this — when packages have conflicting version requirements that prevent installation entirely.

For Vercel deployments where module-not-found errors only appear in CI, check your devDependencies vs dependencies split. Packages needed at runtime must be in dependencies. The Vercel build failed guide walks through the full class of build-time module errors.

Type Errors in TypeScript

TypeScript type errors are caught at compile time, not runtime — which is the entire point of TypeScript. But they can still block your build if you haven't configured strict mode or your types are too loose.

// BEFORE — passing the wrong type to a function
function formatPrice(price: number): string {
  return `$${price.toFixed(2)}`;
}

const rawPrice = "9.99"; // Comes from form input as string
formatPrice(rawPrice);
// TypeScript: Argument of type 'string' is not assignable to parameter of type 'number'

// AFTER — parse the value to the correct type before use
const rawPrice = "9.99";
const price = parseFloat(rawPrice);
if (isNaN(price)) {
  throw new Error("Invalid price value");
}
formatPrice(price); // Now valid
// BEFORE — accessing a property TypeScript doesn't know exists
interface User {
  id: string;
  email: string;
}

function getDisplayName(user: User): string {
  return user.name; // TypeScript: Property 'name' does not exist on type 'User'
}

// AFTER — update the type to match the actual data
interface User {
  id: string;
  email: string;
  name?: string; // Optional — may or may not exist
}

function getDisplayName(user: User): string {
  return user.name ?? user.email; // Fall back to email if name is absent
}

When TypeScript's types don't match your actual data shape (especially with API responses), the temptation is to cast everything with as any. Resist it. The cast silences the error but removes the protection TypeScript provides. Instead, use Zod or io-ts to validate API responses at runtime and generate types from that validation.

A Debugging Workflow That Actually Works

Random debugging — changing one thing, seeing if it helps, changing another thing — is slow and often makes things worse. A structured workflow solves coding errors faster every time.

Step 1: Read the full error message. Don't skim it. Read the error type, the message, and the first line in your code on the stack trace. Write down exactly what the error says in your own words. This forces comprehension.

Step 2: Reproduce it reliably. If you can't trigger the error on demand, you can't verify when it's fixed. Figure out the minimum steps to make it happen. If it's intermittent, that tells you something about the cause — race conditions, network state, or data variations.

Step 3: Isolate the smallest reproducing case. Comment out everything unrelated. Reduce the failing function to its minimum. If you have a 200-line component that crashes, extract the relevant 15 lines into a standalone script. Isolation eliminates variables and speeds up hypothesis testing.

Step 4: Inspect state just before the error. Add a console.log or set a debugger breakpoint immediately before the line that throws. Log the values of every variable the failing line uses. Nine times out of ten, one of those values is not what you assumed.

// Before the error line, add this temporarily:
console.log({
  user,
  userId: user?.id,
  userType: typeof user,
  hasEmail: 'email' in (user ?? {})
});
// This takes 30 seconds and usually reveals the problem immediately

Step 5: Form one hypothesis and test it. Based on what you see in Step 4, propose a specific cause: "I think user is null here because the API call fails silently." Make exactly one change to test that hypothesis. If it works, you found the root cause. If it doesn't, you've ruled something out — that's still progress.

Step 6: Fix and verify. Once the fix works, verify it against your reproduction case from Step 2. Then check surrounding code for the same pattern — if you had a null check missing here, you might have it missing in three other places.

The Chrome DevTools debugger supports breakpoints, watch expressions, and step-through execution that make Step 4 dramatically faster for frontend code. Learn to use it — relying only on console.log is the debugging equivalent of using alert() for UI.

For systematic approaches to the full debugging lifecycle beyond individual errors, the cursor stop lazy AI placeholders guide covers how to get AI tools to give you real implementations rather than placeholder code that introduces new errors downstream.

When AI Tools Can't Fix the Error

AI coding assistants are best at recognizing patterns they've seen many times before. The classic errors covered above — null references, missing modules, type mismatches — are exactly the kind of thing they handle well. Feed the error message and the relevant code into your AI tool and it will usually give you a reasonable fix.

But AI tools hit a wall when the error depends on context they don't have:

  • Environment-specific errors — an error that only happens in production because of a specific server configuration, a missing environment variable, or a race condition that doesn't reproduce locally. The AI can't see your production environment.
  • Errors deep in library internals — when the stack trace runs through third-party code and the failure is actually a misuse of an API at a layer the AI can't see from your snippet.
  • Logic errors with no crash signal — your function runs and returns a value, but the value is wrong. The AI can't test your output; it can only reason about it.
  • Compounding errors — when multiple things are slightly wrong at once, and fixing one just unmasks another. The AI tends to fix the surface error without understanding the systemic issue.

This isn't a failure of AI tools — it's an architectural constraint. They don't have access to your full runtime environment, your database state, your deployment logs, or the history of changes that got you here.

When you've tried the AI fix, applied it, and the error either persists or transforms into a different error, that's the signal to shift strategies. At that point, you need someone who can look at the whole picture: your logs, your environment, your data flow — not just the isolated code snippet.

The Vercel 504 gateway timeout fix and the Supabase RLS policy guide are good examples of the class of errors that look like coding errors but are actually infrastructure or configuration errors. The code is fine; the environment is wrong. AI tools almost always miss that distinction.

Conclusion

Most coding errors follow a short list of patterns: syntax violations the parser catches immediately, runtime crashes from invalid operations on null or undefined values, and logic bugs that only surface through testing. Getting fast at fixing them comes down to reading error messages carefully instead of Googling the first phrase you see, working through a systematic isolation-and-hypothesis workflow, and knowing the difference between fixing the symptom and fixing the root cause.

The deeper skill is recognizing when the error you're looking at isn't actually the problem. When fixing the null reference reveals a data shape mismatch, which reveals an API response format issue, which turns out to be a missing auth header — that's the 20% where systematic debugging and a second perspective pay off more than another AI prompt.

If you've been circling the same error for more than half an hour, or every fix just trades one error for another, that's a sign the problem is bigger than a single line of code. A 30-minute screen-share with a senior engineer who can see your whole environment — logs, data, deployment, config — will get you further than three more hours solo. That's exactly what FlowQL sessions are for: not rubber-duck debugging, but a real second perspective on the full context of your error.

For the browser-specific runtime errors that show up in Next.js apps, start with the nextjs-window-is-not-defined-fix guide — it covers the SSR/client environment mismatch that causes an entire class of "works locally, breaks in prod" errors.

Still blocked?

This fix didn't work for your setup? Get a senior engineer on your screen in 30 minutes — fixed or refunded.

Reserve My Spot →
Still stuck?

Get a senior engineer on your screen.

30-minute live screen-share sessions with a vetted senior dev. Fixed or fully refunded — no questions asked.

No spam. Just a heads-up when sessions open.

Related Articles