JavaScript errors fall into three distinct categories — syntax, runtime, and logic — and the right tool for finding each one is different. A syntax checker catches structural problems before the engine even runs your code; a runtime decoder explains cryptic error messages after they surface; a static analyser finds potential bugs that neither approach touches. This guide maps every category of JavaScript error to the best tool for catching it, with workflows for the browser, Node.js, and CI/CD pipelines.
Types of JavaScript errors
Every JavaScript error you encounter belongs to one of three categories, and mixing them up leads to using the wrong tool. Understanding the categories first saves significant debugging time because it tells you exactly where to look and what kind of checker will help.
Syntax errors
Syntax errors are caught by the JavaScript parser before a single line of code runs. They mean the engine cannot interpret the structure of the file — a missing closing brace, an unexpected token, a reserved word used as a variable name, or an unclosed string literal. The error is thrown at parse time with a line number and a short description. A syntax checker or validator catches these without running the code.
Runtime errors
Runtime errors are thrown during execution when syntactically valid code tries to perform an illegal operation. The most common: accessing a property on `null` or `undefined` (`TypeError`), calling something that is not a function (`TypeError`), referencing a variable that does not exist (`ReferenceError`), or dividing by a non-numeric value (`NaN` — which fails silently). These errors need a running environment, a stack trace decoder, or careful static analysis to catch.
Logic errors
Logic errors produce the wrong output without throwing any error at all — an off-by-one loop, an incorrect conditional, a mutation where a copy was intended. No validator or syntax checker detects these; they require unit tests, code review, or a debugger session. ESLint's rule set overlaps with some logic error patterns (e.g. flagging `==` instead of `===`), but most logic bugs are only found by running the code with real data.
- SyntaxError: Detected at parse time — missing bracket, bad token, unclosed string.
- TypeError: Accessing `.property` on null/undefined, calling a non-function.
- ReferenceError: Using a variable that was never declared in the current scope.
- RangeError: Passing a value outside an allowed range — e.g. `new Array(-1)`.
- URIError: Malformed URI passed to `decodeURIComponent` or `encodeURIComponent`.
- Logic bug: Wrong output, no thrown error — requires tests or a debugger.
Note
JavaScript syntax checkers
A JavaScript syntax checker (also called a syntax validator) parses your code without executing it and reports every place the structure violates JavaScript grammar. This is the fastest and safest first check — it catches the errors that would prevent your code from running at all, and it does so in milliseconds with no side effects.
When to use a syntax checker
Syntax checkers are useful in four situations: when you receive JavaScript from a third party (a generated file, a snippet from documentation, or code pasted from a colleague), when you are debugging a script that fails silently in a minified build, when you are writing JavaScript in an editor without language server support, and when you want a quick sanity check before committing a file you have been heavily editing.
What a syntax checker does and does not catch
| Check | Syntax validator | ESLint | TypeScript compiler |
|---|---|---|---|
| Missing closing bracket/brace | ✓ Yes | ✓ Yes | ✓ Yes |
| Unexpected token / bad syntax | ✓ Yes | ✓ Yes | ✓ Yes |
| Undefined variable reference | ✗ No | ✓ With no-undef rule | ✓ Yes (strict) |
| Type mismatch | ✗ No | ✗ No | ✓ Yes |
| Unused variable warning | ✗ No | ✓ no-unused-vars | ✓ noUnusedLocals |
| Missing await on async call | ✗ No | ✓ no-floating-promises | ✓ Yes |
| Logic / wrong output | ✗ No | ✗ No | ✗ No |
The JavaScript Syntax Validator on Quasar Tools runs entirely in your browser — paste your code, click validate, and every syntax error is highlighted with line number and parser message in under a second. Your code is never uploaded to a server, which makes it safe for proprietary scripts, internal tooling, and confidential application code.
JavaScript Syntax Validator
Check JavaScript code for syntax errors instantly — browser-local parser, line-level diagnostics, no upload required.
Runtime errors and stack traces
Runtime errors arrive as a thrown exception with a type, a message, and a stack trace. Reading them efficiently is a skill that separates fast debuggers from slow ones. The error type narrows the cause immediately; the stack trace points to the exact execution path that led there.
How to read a JavaScript stack trace
A stack trace is a list of function calls in reverse order — the most recent call at the top, the entry point at the bottom. Each line shows a function name, a file path, and a `line:column` number. Start at the top of the trace and scan down until you find the first line that references your own code (not a library like React, Express, or lodash). That is the call that triggered the error. The lines above it show how you got there.
TypeError: Cannot read properties of undefined (reading 'name')
at formatUser (app.js:24:18) // ← your code — start here
at renderCard (components.js:51:5) // ← your code — call chain
at Array.map (<anonymous>)
at buildList (components.js:44:20)
at App (app.js:12:15)
at React.createElement ...The first line names the error type (`TypeError`) and the specific property that failed (`name`). Line `app.js:24:18` is where the property access happened. Go to that line — `user.name` where `user` is undefined — and add the appropriate guard: optional chaining (`user?.name`), a null check, or a default value. The JavaScript Stack Trace Explainer parses any stack trace and outputs a structured breakdown of the origin, call chain, and the most likely fix automatically.
Decoding cryptic runtime error messages
Some runtime error messages are straightforward; others are famously unhelpful. `"Maximum call stack size exceeded"` means infinite recursion. `"Cannot set properties of null"` means you called `.setAttribute()` or similar on a DOM element that does not exist yet. `"$ is not defined"` in a browser context means jQuery was not loaded before the script that uses it. The JavaScript Runtime Error Decoder accepts any error message and returns a plain-English explanation with specific fix steps for the most common patterns.
Tip
JavaScript Runtime Error Decoder
Paste any JavaScript error message and get a plain-English explanation with targeted fix recommendations — no environment setup needed.
ESLint and static analysis
ESLint is the industry-standard static analysis tool for JavaScript and TypeScript. It reads your source code without executing it and applies a configurable rule set that catches problems ranging from syntax errors to security anti-patterns. Unlike a syntax checker, ESLint understands scope, variable lifecycles, and import graphs — which allows it to find bugs that are invisible to a pure parser.
What ESLint catches that syntax checkers miss
- Undefined variables: The `no-undef` rule flags any variable used without a declaration in scope.
- Unused variables: `no-unused-vars` prevents dead code accumulation that obscures real logic.
- Unsafe equality: `eqeqeq` enforces `===` over `==`, eliminating type-coercion bugs.
- Missing await: `no-floating-promises` (via TypeScript ESLint) catches unhandled async calls.
- Unreachable code: `no-unreachable` flags statements after a `return` or `throw`.
- No-console in production: `no-console` prevents debug logs from shipping to production.
- Security rules: `eslint-plugin-security` flags potential injection vulnerabilities and unsafe regex.
ESLint configuration essentials
ESLint's behaviour is entirely driven by its configuration file — `.eslintrc.json`, `.eslintrc.js`, or the new flat config format `eslint.config.js`. The config declares which rule sets to extend (`eslint:recommended`, `plugin:@typescript-eslint/recommended`, `plugin:react/recommended`) and which individual rules to enable, disable, or adjust. A misconfigured ESLint file can silently disable important rules, which is why validating the config itself matters. The ESLint Config Validator checks your configuration file for structural errors and rule conflicts before you run a lint pass.
ESLint's value is not in the rules you enable — it is in the rules your team agrees to enforce consistently. A shared config committed to version control ensures every developer sees the same feedback.
Running ESLint for the first time
# Install ESLint in a project
npm install --save-dev eslint
# Initialize a config file interactively
npx eslint --init
# Lint a single file
npx eslint src/app.js
# Lint a directory and auto-fix safe issues
npx eslint src/ --fix
# Output as JSON for CI tooling
npx eslint src/ --format json > eslint-report.jsonNote
Browser DevTools debugging
Chrome DevTools, Firefox Developer Tools, and Safari Web Inspector provide the deepest JavaScript debugging experience available — live execution, breakpoints, variable inspection, and network request tracing. For runtime errors that are difficult to reproduce locally, DevTools is the primary investigation environment.
The Console panel
The Console tab shows every logged message, warning, and thrown error in real time. Errors appear in red with an expandable stack trace. Clicking the file:line reference on the right jumps directly to the Sources panel at that location. The Browser Console Error Classifier complements DevTools by categorising console errors by type and severity — useful when you have a long console output and need to triage quickly.
Breakpoints and the Sources panel
The Sources panel lets you set breakpoints — pause execution at a specific line and inspect every variable in scope at that moment. Click the line number gutter in the Sources panel to add a breakpoint, then reload the page or trigger the action that causes the error. When execution pauses, hover over any variable to see its current value, use the Call Stack panel to see how you got there, and step through the code line by line with F10 (step over) or F11 (step into).
Conditional and logpoint breakpoints
Right-click any line number in Sources to add a conditional breakpoint (pauses only when a condition is true, e.g. `user.id === 42`) or a logpoint (logs a value without pausing, like a non-invasive `console.log`). Both are extremely useful for debugging loops and event handlers where pausing on every iteration would be impractical.
| Debugging approach | Best for | Catches runtime errors | Catches syntax errors |
|---|---|---|---|
| JavaScript Syntax Validator | Pre-run static check | ✗ No | ✓ Yes |
| ESLint | Static analysis, CI/CD | ⚠ Some | ✓ Yes |
| Browser DevTools Console | Live browser errors | ✓ Yes | ✓ Yes |
| DevTools Breakpoints | Interactive debugging | ✓ Yes | ✗ No |
| Runtime Error Decoder | Explaining error messages | ✓ Yes | ✗ No |
| Stack Trace Explainer | Tracing call origin | ✓ Yes | ✗ No |
| Node.js --inspect + Chrome | Server-side debugging | ✓ Yes | ✓ Yes |
Error checking in CI/CD
Manual error checking during development is good practice but not a guarantee. Automating JavaScript error checking in your CI/CD pipeline ensures that no syntax error, ESLint violation, or type error can merge into the main branch — regardless of which developer submitted the pull request or whether they ran checks locally.
A minimal CI quality gate
name: JavaScript Quality
on:
pull_request:
paths: ['src/**/*.js', 'src/**/*.ts']
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: ESLint
run: npx eslint src/ --max-warnings 0
- name: TypeScript check
run: npx tsc --noEmitThe `--max-warnings 0` flag treats ESLint warnings as errors, blocking the merge. Pair this with `tsc --noEmit` for TypeScript projects to catch type errors that ESLint's rules cannot see. Both steps exit with a non-zero code on failure, which fails the GitHub Actions workflow and prevents the pull request from being merged until the issues are resolved.
Source maps for production error tracking
When a JavaScript error reaches production in a minified bundle, the stack trace shows compressed file names and column numbers that are useless without a source map. Source maps link the minified output back to the original source files. Before deploying, validate that your source map files are correctly structured using the Source Map Validator — a broken source map produces unreadable production stack traces that make incident diagnosis significantly slower.
Warning
Debugging best practices
Good error-checking habits reduce the time spent debugging by orders of magnitude. These practices apply regardless of whether you are working in a browser, a Node.js service, or a serverless function.
Fix the first error, not all errors
JavaScript syntax errors cascade — one missing bracket at line 10 can produce five reported errors below it as the parser loses track of the document structure. Always fix the topmost reported error first, then re-run the validator. What looked like five bugs is often one. This applies equally to ESLint reports and TypeScript compiler output.
Use strict mode and modern language features
Adding `"use strict"` to a file (or using ES modules, which are always strict) converts silent failures into thrown errors. Assigning to an undeclared variable silently creates a global in sloppy mode; in strict mode it throws a `ReferenceError` immediately. Optional chaining (`?.`), nullish coalescing (`??`), and array destructuring defaults (`const [a = 0] = arr`) reduce the surface area for `TypeError: Cannot read properties of undefined` errors significantly.
Validate external data at the boundary
The majority of runtime `TypeError` exceptions in production come from external data — API responses, user input, or localStorage values — that do not match the expected shape. Validate incoming data at the boundary: use a schema validator like Zod or Joi on API responses, check `localStorage` values before parsing them as JSON, and never assume a field exists just because it was present in your test data. The JavaScript Heap Size Calculator is useful when memory errors appear — it helps estimate whether a large data structure or long-running process is exceeding V8's heap allocation.
- Fix the first error first: Syntax errors cascade — one real problem produces multiple reported ones.
- Enable ESLint in your editor: Real-time inline feedback catches errors as you type, not after commit.
- Use TypeScript: Type errors caught at compile time cannot become runtime errors in production.
- Validate external data: API responses and user input must be checked before use, not assumed correct.
- Write focused tests: Unit tests for critical paths surface logic errors that no static tool can detect.
- Use source maps: Readable production stack traces cut incident response time dramatically.
Tip
Key takeaways
- Syntax errors are caught before execution — use the JavaScript Syntax Validator for instant, browser-local checking with no code upload.
- Runtime errors produce a type and a stack trace — the JavaScript Runtime Error Decoder explains any error message in plain English with fix steps.
- ESLint catches problems that syntax checkers miss: undefined variables, unsafe equality, unused imports, and missing `await` — validate your ESLint config with the ESLint Config Validator.
- Always fix the topmost reported error first — syntax errors cascade and one real problem produces multiple reported ones.
- Add ESLint and `tsc --noEmit` to your CI/CD pipeline so no error can merge without being caught, regardless of local setup.
- Source maps are essential for readable production stack traces — validate them with the Source Map Validator before deployment.
- Validate external data (API responses, user input) at the boundary to eliminate the leading cause of `TypeError: Cannot read properties of undefined` in production.