JavaScript Error Types Table
| Name | Thrown when | Typical example |
|---|---|---|
TypeError | A value is not the type the operation needs | Reading a property of undefined; calling a non-function |
ReferenceError | A NAME does not exist at all | Using a variable that was never declared |
RangeError | Value out of legal range | new Array(-1); Number.toFixed(200) |
SyntaxError | Code cannot parse (or invalid JSON) | JSON.parse('{bad}') at runtime |
URIError | Malformed encode/decode input | decodeURIComponent('%') |
AggregateError | Bundle of failures | Promise.any when every input rejects |
EvalError | Legacy - unused by modern engines | Kept for spec compatibility |
JavaScript ships with seven built-in error types, and their names are a diagnostic system: the type tells you WHICH assumption broke before you read a line of your code. The table below lists all seven with the condition that fires each and the example you will actually meet in production.
Bottom line: the ReferenceError-vs-TypeError split is the fastest diagnosis in JavaScript debugging. ReferenceError means the NAME does not exist - a typo, a missing import, code running before a declaration. TypeError means the name exists but the VALUE is wrong - usually undefined, because some object was missing or a function returned nothing. Read the type first; it halves the search space before you open a file.
The honest part: throw accepts any value, but only Error instances carry .stack - throwing strings feels shorter and costs you the trace. And catch has exactly two respectable shapes: handle the specific case you anticipated, or rethrow what you cannot. A bare catch that swallows everything converts a five-minute bug into a five-hour archaeology dig.
How to use
- Match your console's error name against the table - the Thrown-when column states the broken assumption.
- Apply the ReferenceError/TypeError split: undeclared name versus wrong value; it points at the typo or the missing data.
- Write catch blocks by the discipline in the note: handle specifically, rethrow specifically, never swallow silently.
Frequently asked questions
What is the difference between TypeError and ReferenceError?
ReferenceError: the identifier itself was never declared - console.log(userNme) with a typo, or referencing a module you forgot to import. TypeError: the identifier exists but holds the wrong kind of value - user.name where user is undefined, or calling something that is not a function. The distinction points to different bugs: ReferenceError means fix the NAME (spelling, import, hoisting order); TypeError means fix the VALUE (guard clauses, default values, null checks).
Does finally run if catch throws?
Yes - finally runs no matter what: after the try completes, after catch handles an error, even when catch itself throws a new error, and when try or catch contains return or break. The one thing finally cannot do is stop a pending exception; it runs, then the exception continues. That guarantee is why cleanup code (closing loaders, releasing locks) belongs in finally - and why a return inside finally silently discards any in-flight exception, which is the classic way errors vanish.
Should I throw strings or Error objects?
Error objects, always. Only an Error instance carries a .stack trace pointing at where the problem happened; throw 'oops' produces a rejection whose only content is the string, and the trail ends there. Custom types earn their keep at API boundaries: class ValidationError extends Error lets callers distinguish expected failures (user typed something wrong) from bugs (your code broke) with a clean instanceof check instead of parsing messages.
Why does my Promise rejection show UnhandledPromiseRejection?
Every rejected promise must be observed - by .catch, by try/catch around await, or by a second argument to .then. A rejection nobody observes fires the unhandledrejection event and, in modern runtimes, crashes the process. The sneaky source: calling an async function without awaiting it creates a promise whose eventual rejection has no listener. Fire-and-forget code needs an explicit .catch(() => {}) or a logged handler - silence is a decision you must make on purpose.