**The Error hierarchy**
Every thrown error in JavaScript is (or should be) an instance of `Error` or one of its subclasses:
```javascript
Error // base class — used for custom errors
├── SyntaxError // invalid JS syntax (usually at parse time)
├── ReferenceError // variable not found
├── TypeError // wrong type for an operation
├── RangeError // value out of allowed range
├── URIError // malformed URI
└── EvalError // error inside eval() (rarely seen)
```
**Creating and throwing errors**
```javascript
throw new Error('Something went wrong'); // generic
throw new TypeError('Expected a string'); // specific
throw new RangeError('Index out of bounds'); // specific
// You can throw anything, but an Error object gives you a stack trace:
throw 'oops'; // works, but loses stack trace — avoid this
throw { code: 404 }; // same problem — use new Error() instead
```
**Error properties**
```javascript
try {
null.property; // TypeError
} catch (err) {
console.log(err.name); // 'TypeError'
console.log(err.message); // "Cannot read properties of null (reading 'property')"
console.log(err.stack);
// TypeError: Cannot read properties of null (reading 'property')
// at <anonymous>:2:8
// at ...
}
```
`err.stack` is a string containing the error message plus the call stack — invaluable for debugging. It's not part of the spec but all major engines provide it.
**err.name vs constructor.name**
```javascript
const e = new TypeError('bad');
e.name === 'TypeError' // true — the error type label
e.constructor.name === 'TypeError' // also true — but only if the class isn't minified
// Prefer checking err.name in catch blocks:
if (err instanceof TypeError) { ... } // cleanest — uses prototype chain
if (err.name === 'TypeError') { ... } // also fine
// Don't rely on err.constructor.name in production (minification can change it)
```
**Custom error classes**
```javascript
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError'; // override — important!
this.field = field;
}
}
throw new ValidationError('Required field missing', 'email');
// In catch:
if (err instanceof ValidationError) {
console.log(`Field '${err.field}': ${err.message}`);
}
```
Always set `this.name` in custom subclasses — otherwise `err.name` inherits 'Error' from the base class, which breaks string-based type checking.
try/catch/finally, re-throwing, and unhandled promise rejections
**Error handling strategies**
Three patterns exist for communicating failures. Each has a place:
```
1. Throw — for unexpected/unrecoverable errors (programming bugs)
2. Return null/undefined — simple sentinel, but loses error details
3. Result object {ok, error} — explicit, typed, forces the caller to check
```
**The Result pattern**
Instead of throwing for expected failures, return a discriminated union:
```javascript
function parseUserInput(str) {
if (!str.trim()) {
return { ok: false, error: 'Input is empty' };
}
const num = Number(str);
if (isNaN(num)) {
return { ok: false, error: `'${str}' is not a number` };
}
return { ok: true, value: num };
}
const result = parseUserInput(userStr);
if (!result.ok) {
showError(result.error);
} else {
process(result.value);
}
```
Use the Result pattern for **expected failures** (form validation, parsing, API responses that may not exist). Use `throw` for **unexpected state** (programmer errors, violated invariants).
**Error boundary pattern — top-level async handler**
```javascript
async function main() {
try {
const config = await loadConfig();
await startServer(config);
} catch (err) {
console.error('Fatal error:', err);
process.exit(1);
}
}
main();
```
**Wrapping errors to preserve context**
ES2022 added `Error.cause` for chaining errors:
```javascript
async function loadUserProfile(id) {
try {
return await db.users.findById(id);
} catch (err) {
throw new Error(`Failed to load user ${id}`, { cause: err });
}
}
// In catch:
console.error(err.message); // 'Failed to load user 42'
console.error(err.cause.message); // original DB error message
```
**Quick decision guide**
```
Scenario Recommended approach
────────────────────────────── ────────────────────────────────
User input validation Result pattern {ok, error}
Network/DB failures throw (async boundary catches)
Programmer error (assert-like) throw Error with descriptive msg
Optional data that may not exist return null (simple case)
Multiple possible failure modes Result with typed error
```
Write a `try/catch/finally` block that parses a JSON string. If parsing fails, return a default value `{}`. The `finally` block should log 'parse attempted'.
function safeParse(str) {
// your code here
}
console.log(safeParse('{"a":1}')); // { a: 1 }
console.log(safeParse('invalid')); // {}
// 'parse attempted' should be logged in both cases
Write a function that takes an array and an index. If the index is out of bounds, throw a `RangeError`. If the argument is not an array, throw a `TypeError`.
function getElement(arr, index) {
if (!Array.isArray(arr)) throw new TypeError('Expected an array');
if (index < 0 || index >= arr.length) throw new RangeError(`Index ${index} out of bounds`);
return arr[index];
}
Write a function that wraps another function and catches any TypeError, returning a default value instead. Other error types should propagate normally.
function withTypeErrorFallback(fn, defaultValue) {
// your code here
}
const safe = withTypeErrorFallback(() => null.length, 0);
console.log(safe); // 0
try {
withTypeErrorFallback(() => { throw new RangeError('oops'); }, 0);
} catch (e) {
console.log(e instanceof RangeError); // true
}
Solution
function withTypeErrorFallback(fn, defaultValue) {
try {
return fn();
} catch (e) {
if (e instanceof TypeError) return defaultValue;
throw e;
}
}
Implement error chaining: create a function that wraps a low-level error in a higher-level one, preserving the original error as `cause`.
function readConfig(filename) {
throw new Error(`File not found: ${filename}`);
}
function loadApp(configPath) {
// Try readConfig(configPath). If it fails, throw a new Error
// 'Failed to load application' with the original as 'cause'
}
try {
loadApp('config.json');
} catch (e) {
console.log(e.message); // 'Failed to load application'
console.log(e.cause.message); // 'File not found: config.json'
}
Solution
function readConfig(filename) {
throw new Error(`File not found: ${filename}`);
}
function loadApp(configPath) {
try {
return readConfig(configPath);
} catch (e) {
throw new Error('Failed to load application', { cause: e });
}
}
Write an `assertType` function that throws a `TypeError` with a descriptive message if a value is not of the expected type (checked with `typeof`).
function assertType(value, expectedType) {
// your code here
}
assertType(42, 'number'); // ok, no throw
assertType('hello', 'string'); // ok
try {
assertType(42, 'string');
} catch (e) {
console.log(e instanceof TypeError); // true
console.log(e.message); // e.g. 'Expected string, got number'
}
Solution
function assertType(value, expectedType) {
const actual = typeof value;
if (actual !== expectedType) {
throw new TypeError(`Expected ${expectedType}, got ${actual}`);
}
}
No split tab
Cookie preferences
We use necessary cookies to run the site. With your permission, we can also save your site preferences and use analytics and advertising cookies to understand usage and support the project.
Open tools in tabs.Exercises, IDE tools, and trainers stay available as site tabs.
Switch without losing context.Move between explanations, code, and utilities while keeping your place.
Use the sidebar as your map.The left panels hold navigation, settings, files, libraries, and tool controls.
PythonJavaScriptSQLite
One IDE, three practical modes
Python in the browser.Run small scripts, try libraries, and practice API requests without installing anything.
JavaScript for quick experiments.Test browser-friendly code and compare ideas next to your learning materials.
SQLite for data practice.Open the database explorer to inspect tables, write queries, and learn SQL workflows locally.
TopicIDE
Work side by side with split tabs
Keep instructions visible.Open an exercise or reference page beside the IDE instead of jumping back and forth.
Compare tools while you learn.Place regex checks, explanations, and code experiments next to each other when the task needs it.
Close the split when you are done.The workspace returns to a single focused tab, and your open site tabs remain available.
Code Typing Trainer
Or plain text
This trainer is designed for a physical keyboard.Open this section on a laptop or desktop with a wide screen. Touch typing practice will not work correctly on a phone.
Speed: 0 chars/min
0 words/min
Best speed (60s): 0 chars/min
0 words/min
Errors: 0
Total time: 0.0 s
To practice touch typing, avoid looking at your physical keyboard.