JavaScript Errors and Debugging
Writing bug-free code is impossible — even the creator of JavaScript produced the typeof null === "object" bug in just 10 days. So learning to find bugs and handle errors is even more important than learning to write code.
📖 Summary
Common Error Types
| Error Type | Cause | Example |
|---|---|---|
SyntaxError |
Invalid syntax | if (true { — mismatched parentheses |
ReferenceError |
Accessing a non-existent variable | console.log(notExist) |
TypeError |
Operating on the wrong type | undefined.toString() |
RangeError |
Value out of valid range | new Array(-1) |
SyntaxError is caught before code execution (parsing phase). The other three are runtime errors.
try...catch...finally
Use try...catch to catch runtime errors and prevent your program from crashing:
<script>
try {
const data = JSON.parse('{bad json}');
} catch (error) {
console.log("Parse failed:", error.message);
} finally {
console.log("Cleanup work");
}
</script>
The error object in catch has two commonly used properties:
error.message— a description of the errorerror.name— the error type name
throw — Manually Throwing Errors
try...catch can only catch runtime errors, but business logic errors need to be thrown manually:
<script>
function setAge(age) {
if (typeof age !== "number" || age < 0) {
throw new Error("Age must be a positive number");
}
console.log("Age set to: " + age);
}
try {
setAge(-5);
} catch (e) {
console.log(e.message);
}
</script>
throw can throw any value, but using new Error() or its subclasses is recommended — they include stack trace information.
Custom Error Messages
Use subclasses of Error or custom properties to provide richer error information:
<script>
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
try {
throw new ValidationError("email", "Invalid email format");
} catch (e) {
console.log(e.name + " [" + e.field + "]: " + e.message);
}
</script>
Browser DevTools (F12)
| Panel | Purpose |
|---|---|
| Console | View logs, execute code, see errors |
| Sources | Set breakpoints, step through code, inspect variables |
| Network | Inspect network requests, response status, load times |
| Elements | View/modify DOM and styles |
Breakpoint debugging is the most powerful debugging technique: click a line number in the Sources panel to set a breakpoint, refresh the page, and the code pauses at that point — step through line by line, inspect variables, and examine the call stack.
The console Family
| Method | Purpose |
|---|---|
console.log() |
General output |
console.warn() |
Warning (yellow) |
console.error() |
Error (red) |
console.table() |
Display arrays/objects as a table |
console.time() / console.timeEnd() |
Timing |
console.group() / console.groupEnd() |
Grouped output |
console.clear() |
Clear the console |
console.table is especially useful for displaying arrays of objects — far more readable than log.
Debugging Tips
- Comment out halves: Comment out half the code, see which half has the problem, and narrow it down step by step
- console.log everywhere: Print variable values at key points — primitive but effective
- Breakpoint debugging: More powerful than
console.log— pause execution and modify variables in real time - Rubber duck debugging: Explain your code line by line to a rubber duck (or a colleague). Often, you'll find the bug halfway through your explanation.
Example: try/catch for JSON Parsing
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSON Parsing</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.demo { max-width: 600px; margin: 16px auto; }
textarea { width: 100%; height: 120px; padding: 12px; font-size: 14px; font-family: monospace; border: 2px solid #ccc; border-radius: 6px; resize: vertical; box-sizing: border-box; }
button { padding: 10px 24px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; margin: 8px 4px; }
.parse { background: #4a90d9; color: #fff; }
.parse:hover { background: #357abd; }
.bad { background: #d9534f; color: #fff; }
.bad:hover { background: #c9302c; }
.result { margin-top: 16px; padding: 16px; border-radius: 8px; }
.success { background: #d4edda; border: 2px solid #5cb85c; }
.error-box { background: #f8d7da; border: 2px solid #d9534f; }
.error-type { font-weight: bold; color: #721c24; }
.error-msg { margin-top: 4px; color: #721c24; }
</style>
</head>
<body>
<h2 style="text-align:center;">JSON Parse Error Handling</h2>
<div class="demo">
<textarea id="jsonInput">{
"name": "Alice",
"age": 20,
"skills": ["JavaScript", "HTML", "CSS"]
}</textarea>
<div style="text-align:center;">
<button class="parse" id="parseBtn">Parse JSON</button>
<button class="bad" id="badBtn">Insert Invalid JSON</button>
</div>
<div id="result"></div>
</div>
<script>
function parseJSON(jsonStr) {
try {
const data = JSON.parse(jsonStr);
return { success: true, data: data };
} catch (error) {
return { success: false, name: error.name, message: error.message };
}
}
document.getElementById("parseBtn").addEventListener("click", function() {
var input = document.getElementById("jsonInput").value;
var result = parseJSON(input);
var output = document.getElementById("result");
if (result.success) {
output.className = "result success";
output.innerHTML = "<strong>Parse successful!</strong><pre>" +
JSON.stringify(result.data, null, 2) + "</pre>";
} else {
output.className = "result error-box";
output.innerHTML = '<div class="error-type">' + result.name +
'</div><div class="error-msg">' + result.message + '</div>';
}
});
document.getElementById("badBtn").addEventListener("click", function() {
document.getElementById("jsonInput").value = '{name: "Alice", age: 20}';
});
</script>
</body>
</html>
Example: Custom Errors and Form Validation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Errors</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.form { max-width: 440px; margin: 16px auto; padding: 24px; background: #f9f9f9; border-radius: 12px; }
.row { margin: 12px 0; }
label { display: block; margin-bottom: 4px; font-weight: bold; }
input { width: 100%; padding: 10px 12px; font-size: 16px; border: 2px solid #ccc; border-radius: 6px; box-sizing: border-box; }
input.valid { border-color: #5cb85c; }
input.invalid { border-color: #d9534f; }
.field-error { color: #d9534f; font-size: 13px; margin-top: 2px; min-height: 20px; }
button { padding: 10px 24px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; background: #4a90d9; color: #fff; width: 100%; margin-top: 8px; }
button:hover { background: #357abd; }
.global-msg { margin-top: 12px; padding: 12px; border-radius: 6px; text-align: center; }
.global-ok { background: #d4edda; color: #155724; }
.global-fail { background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<h2 style="text-align:center;">Custom Error Validation</h2>
<div class="form">
<div class="row">
<label>Username (3–20 characters):</label>
<input type="text" id="username" placeholder="Enter username" />
<div class="field-error" id="usernameErr"></div>
</div>
<div class="row">
<label>Age (0–150):</label>
<input type="text" id="age" placeholder="Enter age" />
<div class="field-error" id="ageErr"></div>
</div>
<div class="row">
<label>Email:</label>
<input type="text" id="email" placeholder="Enter email" />
<div class="field-error" id="emailErr"></div>
</div>
<button id="submitBtn">Submit</button>
<div class="global-msg" id="globalMsg"></div>
</div>
<script>
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
function validateUsername(value) {
if (!value.trim()) throw new ValidationError("username", "Username cannot be empty");
if (value.trim().length < 3) throw new ValidationError("username", "Username must be at least 3 characters");
if (value.trim().length > 20) throw new ValidationError("username", "Username must be at most 20 characters");
}
function validateAge(value) {
if (!value.trim()) throw new ValidationError("age", "Age cannot be empty");
var num = Number(value);
if (isNaN(num)) throw new ValidationError("age", "Please enter a valid number");
if (num < 0 || num > 150) throw new ValidationError("age", "Age range: 0–150");
if (!Number.isInteger(num)) throw new ValidationError("age", "Age must be an integer");
}
function validateEmail(value) {
if (!value.trim()) throw new ValidationError("email", "Email cannot be empty");
if (!value.includes("@")) throw new ValidationError("email", "Email must contain @");
if (!value.includes(".")) throw new ValidationError("email", "Invalid email format");
}
var validators = {
username: validateUsername,
age: validateAge,
email: validateEmail
};
function clearErrors() {
["username", "age", "email"].forEach(function(field) {
document.getElementById(field).className = "";
document.getElementById(field + "Err").textContent = "";
});
document.getElementById("globalMsg").innerHTML = "";
document.getElementById("globalMsg").className = "global-msg";
}
document.getElementById("submitBtn").addEventListener("click", function() {
clearErrors();
var hasError = false;
["username", "age", "email"].forEach(function(field) {
var value = document.getElementById(field).value;
try {
validators[field](value);
document.getElementById(field).className = "valid";
} catch (e) {
hasError = true;
document.getElementById(field).className = "invalid";
document.getElementById(field + "Err").textContent =
e.name + ": " + e.message;
}
});
var msg = document.getElementById("globalMsg");
if (hasError) {
msg.className = "global-msg global-fail";
msg.textContent = "Form has errors — please fix and retry";
} else {
msg.className = "global-msg global-ok";
msg.textContent = "All fields passed validation!";
}
});
</script>
</body>
</html>
Example: Error Types Quick Reference and console Methods
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Error Types & Console</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.section { margin: 20px 0; }
table { border-collapse: collapse; width: 100%; max-width: 700px; }
td, th { border: 1px solid #ddd; padding: 10px 14px; text-align: left; }
th { background: #4a90d9; color: #fff; }
.trigger { background: #f8d7da; }
.safe { background: #d4edda; }
button { padding: 6px 16px; border: none; border-radius: 4px; cursor: pointer; margin: 2px; font-size: 14px; }
.btn-err { background: #d9534f; color: #fff; }
.btn-safe { background: #5cb85c; color: #fff; }
.btn-info { background: #5bc0de; color: #fff; }
.output { margin-top: 12px; padding: 12px; background: #1a1a2e; color: #e0e0e0; border-radius: 6px; font-family: monospace; font-size: 13px; min-height: 60px; max-height: 200px; overflow-y: auto; }
.log-line { margin: 2px 0; }
.log-warn { color: #f0ad4e; }
.log-error { color: #d9534f; }
.log-info { color: #5bc0de; }
</style>
</head>
<body>
<h2>Error Types & Console Methods Demo</h2>
<div class="section">
<h3>Common Error Types</h3>
<table>
<tr><th>Error Type</th><th>Trigger Code</th><th>try/catch Result</th></tr>
<tr class="trigger"><td>SyntaxError</td><td><code>JSON.parse("{bad")</code></td><td id="r1"></td></tr>
<tr class="trigger"><td>ReferenceError</td><td><code>notExistVar</code></td><td id="r2"></td></tr>
<tr class="trigger"><td>TypeError</td><td><code>undefined.toString()</code></td><td id="r3"></td></tr>
<tr class="trigger"><td>RangeError</td><td><code>new Array(-1)</code></td><td id="r4"></td></tr>
</table>
</div>
<div class="section">
<h3>console Methods (Simulated Output)</h3>
<button class="btn-info" onclick="simLog('log', 'Regular log')">console.log</button>
<button class="btn-info" onclick="simLog('warn', 'Warning message')">console.warn</button>
<button class="btn-err" onclick="simLog('error', 'Error message')">console.error</button>
<button class="btn-info" onclick="simLog('info', 'Info message')">console.info</button>
<button class="btn-safe" onclick="simTable()">console.table</button>
<button class="btn-safe" onclick="simTime()">console.time</button>
<button class="btn-info" onclick="clearSim()">Clear</button>
<div class="output" id="simConsole"></div>
</div>
<script>
var tests = [
function() { JSON.parse("{bad"); },
function() { notExistVar; },
function() { var x; x.toString(); },
function() { new Array(-1); }
];
var ids = ["r1", "r2", "r3", "r4"];
var names = ["SyntaxError", "ReferenceError", "TypeError", "RangeError"];
tests.forEach(function(fn, i) {
try {
fn();
document.getElementById(ids[i]).textContent = "Not triggered (unexpected)";
} catch (e) {
document.getElementById(ids[i]).innerHTML =
'<span class="safe">' + e.name + ": " + e.message + '</span>';
}
});
function simLog(type, msg) {
var cls = type === "warn" ? "log-warn" :
type === "error" ? "log-error" : "log-info";
var prefix = type === "warn" ? "⚠" :
type === "error" ? "✖" : "ℹ";
addLine(prefix + " " + msg, cls);
if (type !== "error") {
console[type](msg);
}
}
function simTable() {
var data = [
{ name: "Alice", age: 20, score: 90 },
{ name: "Bob", age: 22, score: 85 },
{ name: "Charlie", age: 21, score: 95 }
];
addLine("console.table:", "log-info");
data.forEach(function(d) {
addLine(" " + d.name + " | " + d.age + " | " + d.score, "log-info");
});
console.table(data);
}
function simTime() {
console.time("demo");
var sum = 0;
for (var i = 0; i < 1000000; i++) { sum += i; }
console.timeEnd("demo");
addLine("⏱ console.time/timeEnd: 1,000,000 additions done", "log-info");
}
function addLine(text, cls) {
var div = document.getElementById("simConsole");
div.innerHTML += '<div class="log-line ' + (cls || "") + '">' + text + '</div>';
div.scrollTop = div.scrollHeight;
}
function clearSim() {
document.getElementById("simConsole").innerHTML = "";
}
</script>
</body>
</html>
❓ FAQ
SyntaxError is thrown during the parsing phase before execution even begins, so the try block is never reached. However, if a SyntaxError occurs at runtime (e.g. JSON.parse fails, or new Function("bad syntax")), it can be caught.catch alone is sufficient.return is for normal flow — the caller handles the returned value. throw is for abnormal flow — it skips subsequent code and walks up the call stack looking for a catch. Use throw for errors, return for normal results.📝 Exercises
- Write a function
safeDivide(a, b)that uses try...catch to handle division by zero (hint: dividing by zero in JS doesn't throw — you need to check and throw yourself). Return the result or an error message. - Write a function
safeJSONParse(str)that parses a JSON string. On failure, return{ success: false, error: <error message> }. On success, return{ success: true, data: <parsed result> }. - Write a function
retry(fn, times): executefn, and if it throws, retry up totimestimes. If all attempts fail, throw the last error. Implement with try...catch.



