JavaScript Exception Handling Practice Programs with Solutions (30 Programs)

Practise JavaScript exception handling with 30 programs, each with a full solution, the exact output and a short explanation. The programs start with the basics, move to intermediate problems and finish with real-world scenarios from test automation, such as test data, prices, form inputs and test results. They are written for testers learning JavaScript for Playwright.

Advertisement

How to practise: read the task, write your own solution first, then open Show solution to compare. Run any program with node program.js.

New to this topic? Read the JavaScript Exception Handling lesson first.

JavaScript Beginner Programs

1. Basic try/catchBeginner

Parse invalid JSON inside try/catch and print the error name.

Show solution
try {
  JSON.parse('{ invalid json }');
} catch (error) {
  console.log('Caught:', error.name);
}
console.log('Program continues');

Output

Caught: SyntaxError
Program continues

Explanation: catch receives the thrown error, so the program can continue instead of crashing.

2. finally always runsBeginner

Show that finally runs both when an error happens and when it does not.

Show solution
for (const input of ['{"ok":true}', 'oops']) {
  try {
    JSON.parse(input);
    console.log('Parsed', input);
  } catch (error) {
    console.log('Failed to parse', input);
  } finally {
    console.log('Cleanup done');
  }
}

Output

Parsed {"ok":true}
Cleanup done
Failed to parse oops
Cleanup done

Explanation: finally is the place for cleanup, such as closing files or browsers, because it always runs.

3. Throw a custom errorBeginner

Write divide(a, b) that throws an error when b is 0.

Show solution
function divide(a, b) {
  if (b === 0) throw new Error('Cannot divide by zero');
  return a / b;
}
try {
  console.log(divide(10, 2));
  console.log(divide(5, 0));
} catch (error) {
  console.log('Error:', error.message);
}

Output

5
Error: Cannot divide by zero

Explanation: throw stops the function and passes the error to the nearest catch.

4. Error propertiesBeginner

Print the name and message of a caught error.

Show solution
try {
  throw new Error('Login button not found');
} catch (error) {
  console.log(error.name);
  console.log(error.message);
  console.log(error instanceof Error);
}

Output

Error
Login button not found
true

Explanation: every Error has a name, a message and a stack trace.

5. ReferenceErrorBeginner

Use an undefined variable inside try and print the error type.

Show solution
try {
  console.log(undefinedVariable);
} catch (error) {
  console.log(error.name);
}

Output

ReferenceError

Explanation: using a name that was never declared throws a ReferenceError.

6. TypeErrorBeginner

Call a method on undefined and print the error type.

Show solution
const response = {};
try {
  response.data.items.length;
} catch (error) {
  console.log(error.name);
}

Output

TypeError

Explanation: reading a property of undefined throws a TypeError, one of the most common test-script errors.

7. RangeErrorBeginner

Create an array with a negative length and catch the error.

Show solution
try {
  new Array(-1);
} catch (error) {
  console.log(error.name, '-', error.message);
}

Output

RangeError - Invalid array length

Explanation: a RangeError means a value is outside the allowed range.

8. Validate input and throwBeginner

Throw a TypeError when age is not a number.

Show solution
function setAge(age) {
  if (typeof age !== 'number') throw new TypeError('age must be a number');
  return `Age set to ${age}`;
}
for (const value of [30, 'thirty']) {
  try {
    console.log(setAge(value));
  } catch (error) {
    console.log(`${error.name}: ${error.message}`);
  }
}

Output

Age set to 30
TypeError: age must be a number

Explanation: throwing the most specific built-in error type tells callers what went wrong.

9. catch without a bindingBeginner

Use catch without naming the error variable when you do not need it.

Show solution
function isValidJson(text) {
  try {
    JSON.parse(text);
    return true;
  } catch {
    return false;
  }
}
console.log(isValidJson('[1, 2]'));
console.log(isValidJson('[1, 2'));

Output

true
false

Explanation: modern JavaScript allows catch { } without (error) when the error itself is not used.

10. Return a default on failureBeginner

Read a number from text and fall back to 0 when it is invalid.

Show solution
function toNumberOrZero(text) {
  try {
    const n = Number(text);
    if (Number.isNaN(n)) throw new Error('not a number');
    return n;
  } catch {
    return 0;
  }
}
console.log(toNumberOrZero('42'), toNumberOrZero('abc'));

Output

42 0

Explanation: catching and returning a default keeps the program running with a safe value.

JavaScript Intermediate Programs

11. Custom error classIntermediate

Create ValidationError with a field property and catch it.

Show solution
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}
try {
  throw new ValidationError('email', 'Email is required');
} catch (error) {
  console.log(error.name, error.field, error.message);
}

Output

ValidationError email Email is required

Explanation: custom error classes carry extra details and can be identified by type.

12. Handle different error typesIntermediate

Handle ValidationError and other errors differently with instanceof.

Show solution
class ValidationError extends Error {}
function run(action) {
  try {
    action();
  } catch (error) {
    if (error instanceof ValidationError) console.log('Fix your input:', error.message);
    else if (error instanceof TypeError) console.log('Bug in code:', error.message);
    else console.log('Unknown error:', error.message);
  }
}
run(() => { throw new ValidationError('password too short'); });
run(() => { null.click(); });
run(() => { throw new Error('network down'); });

Output

Fix your input: password too short
Bug in code: Cannot read properties of null (reading 'click')
Unknown error: network down

Explanation: instanceof lets one catch block react differently to each kind of error.

13. Rethrow unexpected errorsIntermediate

Handle only SyntaxError while parsing and rethrow anything else.

Show solution
function parseConfig(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    if (error instanceof SyntaxError) return {};
    throw error;
  }
}
console.log(parseConfig('{"retries":2}'));
console.log(parseConfig('not json'));

Output

{ retries: 2 }
{}

Explanation: catch only what you can handle. Rethrowing other errors keeps real bugs visible.

14. Error cause chainingIntermediate

Wrap a low-level error in a higher-level one using cause.

Show solution
function loadUser() {
  try {
    JSON.parse('{bad');
  } catch (error) {
    throw new Error('Could not load user', { cause: error });
  }
}
try {
  loadUser();
} catch (error) {
  console.log(error.message);
  console.log('Caused by:', error.cause.name);
}

Output

Could not load user
Caused by: SyntaxError

Explanation: cause preserves the original error, so you get a clear message without losing the root cause.

15. Nested try/catchIntermediate

Use an inner try/catch for an optional step and an outer one for critical steps.

Show solution
try {
  console.log('Step 1: open page');
  try {
    throw new Error('cookie banner not found');
  } catch (inner) {
    console.log('Optional step skipped:', inner.message);
  }
  console.log('Step 2: login');
  throw new Error('login failed');
} catch (outer) {
  console.log('Test failed:', outer.message);
}

Output

Step 1: open page
Optional step skipped: cookie banner not found
Step 2: login
Test failed: login failed

Explanation: the inner catch handles a recoverable problem, while the outer one still catches fatal errors.

16. finally with returnIntermediate

Show that finally runs even when try returns a value.

Show solution
function getStatus() {
  try {
    return 'from try';
  } finally {
    console.log('finally runs before the value is returned');
  }
}
console.log(getStatus());

Output

finally runs before the value is returned
from try

Explanation: finally runs before the function actually returns, which guarantees cleanup.

17. Collect errors instead of stoppingIntermediate

Validate several fields and collect all errors.

Show solution
const checks = [
  () => { if (!'qa@test.com'.includes('@')) throw new Error('bad email'); },
  () => { if ('123'.length < 8) throw new Error('password too short'); },
  () => { if (Number('abc') !== Number('abc')) throw new Error('age is not a number'); }
];
const errors = [];
for (const check of checks) {
  try { check(); } catch (error) { errors.push(error.message); }
}
console.log(errors.join(' | '));

Output

password too short | age is not a number

Explanation: catching inside the loop lets every check run, like Playwright’s soft assertions.

18. Error-first result objectsIntermediate

Return { ok, value, error } instead of throwing.

Show solution
function safeParse(text) {
  try {
    return { ok: true, value: JSON.parse(text) };
  } catch (error) {
    return { ok: false, error: error.message.split(' in JSON')[0] };
  }
}
console.log(safeParse('[1]').ok);
console.log(safeParse('{oops').ok);

Output

true
false

Explanation: result objects make failures part of the normal return value, which is handy in data pipelines.

19. Assertion functionIntermediate

Write assert(condition, message) that throws when the condition is false.

Show solution
function assert(condition, message) {
  if (!condition) throw new Error(`Assertion failed: ${message}`);
}
try {
  assert(2 + 2 === 4, 'maths works');
  console.log('First assertion passed');
  assert('Login'.length === 4, 'title length');
} catch (error) {
  console.log(error.message);
}

Output

First assertion passed
Assertion failed: title length

Explanation: tests are built on assertions that throw when expectations are not met.

JavaScript Real-World QA Scenario Programs

20. Retry on failureReal-world

Retry a flaky action up to 3 times before giving up.

Show solution
let calls = 0;
function clickButton() {
  calls++;
  if (calls < 3) throw new Error(`element detached (call ${calls})`);
  return 'clicked';
}
let result;
for (let attempt = 1; attempt <= 3; attempt++) {
  try {
    result = clickButton();
    break;
  } catch (error) {
    console.log(`Attempt ${attempt} failed: ${error.message}`);
  }
}
console.log('Result:', result);

Output

Attempt 1 failed: element detached (call 1)
Attempt 2 failed: element detached (call 2)
Result: clicked

Explanation: catch, log and retry. break leaves the loop as soon as an attempt succeeds.

21. Safe JSON test-data loaderReal-world

Load JSON test data and report a clear message for invalid content.

Show solution
function loadTestData(name, text) {
  try {
    const data = JSON.parse(text);
    if (!Array.isArray(data)) throw new Error('expected an array');
    return data;
  } catch (error) {
    console.log(`${name}: ${error instanceof SyntaxError ? 'invalid JSON' : error.message}`);
    return [];
  }
}
console.log(loadTestData('users.json', '[{"id":1}]').length);
console.log(loadTestData('broken.json', '[{"id":1}').length);
console.log(loadTestData('object.json', '{"id":1}').length);

Output

1
broken.json: invalid JSON
0
object.json: expected an array
0

Explanation: clear error messages save time when test data files are malformed.

22. Validate a signup formReal-world

Throw a ValidationError for the first invalid field and show it to the user.

Show solution
class ValidationError extends Error {
  constructor(field, message) { super(message); this.name = 'ValidationError'; this.field = field; }
}
function validate({ email, password }) {
  if (!email?.includes('@')) throw new ValidationError('email', 'Enter a valid email');
  if (!password || password.length < 8) throw new ValidationError('password', 'Use at least 8 characters');
  return 'Form is valid';
}
for (const form of [{ email: 'qa', password: '12345678' }, { email: 'qa@test.com', password: '123' }, { email: 'qa@test.com', password: 'Secret123' }]) {
  try { console.log(validate(form)); }
  catch (e) { console.log(`[${e.field}] ${e.message}`); }
}

Output

[email] Enter a valid email
[password] Use at least 8 characters
Form is valid

Explanation: a field name on the error lets the UI show the message next to the right input.

23. Map HTTP status codes to errorsReal-world

Throw a specific error for each failing status code.

Show solution
class HttpError extends Error {
  constructor(status) { super(`HTTP ${status}`); this.status = status; }
}
function checkResponse(status) {
  if (status >= 200 && status < 300) return 'OK';
  throw new HttpError(status);
}
for (const status of [200, 404, 503]) {
  try {
    console.log(status, checkResponse(status));
  } catch (error) {
    const hint = error.status >= 500 ? 'server problem, retry later' : 'check the request';
    console.log(status, error.message, '-', hint);
  }
}

Output

200 OK
404 HTTP 404 - check the request
503 HTTP 503 - server problem, retry later

Explanation: a status code on the error lets callers decide whether to retry or fail fast.

24. Always release resourcesReal-world

Simulate opening and closing a browser, closing it even when the test fails.

Show solution
function runTest(shouldFail) {
  console.log('Browser opened');
  try {
    if (shouldFail) throw new Error('assertion failed');
    console.log('Test passed');
  } catch (error) {
    console.log('Test failed:', error.message);
  } finally {
    console.log('Browser closed');
  }
}
runTest(false);
runTest(true);

Output

Browser opened
Test passed
Browser closed
Browser opened
Test failed: assertion failed
Browser closed

Explanation: finally guarantees cleanup, which is what Playwright fixtures do after use().

25. Soft assertionsReal-world

Check several page values and report all failures at the end.

Show solution
const page = { title: 'Shop', cartCount: 2, user: 'guest' };
const expected = { title: 'Shop', cartCount: 3, user: 'asha' };
const failures = [];
for (const key of Object.keys(expected)) {
  try {
    if (page[key] !== expected[key]) throw new Error(`${key}: expected ${expected[key]}, got ${page[key]}`);
  } catch (error) {
    failures.push(error.message);
  }
}
console.log(failures.length ? `${failures.length} failures\n- ${failures.join('\n- ')}` : 'All checks passed');

Output

2 failures
- cartCount: expected 3, got 2
- user: expected asha, got guest

Explanation: collecting failures shows every problem in one run, like expect.soft in Playwright.

26. Timeout errorReal-world

Throw a TimeoutError when a simulated wait exceeds its limit.

Show solution
class TimeoutError extends Error {
  constructor(ms) { super(`Timed out after ${ms} ms`); this.name = 'TimeoutError'; }
}
function waitForElement(appearsAfterMs, timeoutMs) {
  if (appearsAfterMs > timeoutMs) throw new TimeoutError(timeoutMs);
  return `Element visible after ${appearsAfterMs} ms`;
}
for (const appears of [800, 7000]) {
  try { console.log(waitForElement(appears, 5000)); }
  catch (e) { console.log(`${e.name}: ${e.message}`); }
}

Output

Element visible after 800 ms
TimeoutError: Timed out after 5000 ms

Explanation: a named error type makes timeouts easy to recognise in reports and retry logic.

27. Error message for a missing elementReal-world

Build a helpful error message that lists the available options when a menu item is missing.

Show solution
function clickMenuItem(menu, name) {
  if (!menu.includes(name)) {
    throw new Error(`Menu item '${name}' not found. Available: ${menu.join(', ')}`);
  }
  return `Clicked ${name}`;
}
const menu = ['Home', 'Shop', 'Contact'];
try {
  console.log(clickMenuItem(menu, 'Shop'));
  console.log(clickMenuItem(menu, 'Blog'));
} catch (error) {
  console.log(error.message);
}

Output

Clicked Shop
Menu item 'Blog' not found. Available: Home, Shop, Contact

Explanation: including context in error messages turns a vague failure into an obvious fix.

28. Fail fast on missing configReal-world

Throw at start-up when required environment variables are missing.

Show solution
function requireEnv(env, names) {
  const missing = names.filter(name => !env[name]);
  if (missing.length) throw new Error(`Missing environment variables: ${missing.join(', ')}`);
  return 'Config OK';
}
try {
  console.log(requireEnv({ BASE_URL: 'https://qa.app.test', USERNAME: 'qa' }, ['BASE_URL', 'USERNAME']));
  console.log(requireEnv({ BASE_URL: 'https://qa.app.test' }, ['BASE_URL', 'USERNAME', 'PASSWORD']));
} catch (error) {
  console.log(error.message);
}

Output

Config OK
Missing environment variables: USERNAME, PASSWORD

Explanation: failing immediately with a clear message beats confusing failures halfway through a test run.

29. Log errors with contextReal-world

Wrap a step so errors include the step name.

Show solution
function step(name, fn) {
  try {
    fn();
    console.log(`✔ ${name}`);
  } catch (error) {
    throw new Error(`Step "${name}" failed: ${error.message}`);
  }
}
try {
  step('open home page', () => {});
  step('add item to cart', () => { throw new Error('button disabled'); });
  step('checkout', () => {});
} catch (error) {
  console.log(error.message);
}

Output

✔ open home page
Step "add item to cart" failed: button disabled

Explanation: adding the step name to the error is what Playwright’s test.step does in reports.

30. Count error types in a logReal-world

Count how many errors of each type appear in a list of caught errors.

Show solution
const caught = [new TypeError('a'), new Error('b'), new TypeError('c'), new RangeError('d')];
const counts = {};
for (const e of caught) counts[e.name] = (counts[e.name] || 0) + 1;
console.log(counts);

Output

{ TypeError: 2, Error: 1, RangeError: 1 }

Explanation: grouping by error.name quickly shows which kind of failure dominates a test run.

Keep Practising

2 thoughts on “JavaScript Exception Handling Practice Programs with Solutions (30 Programs)”

Leave a Comment