JavaScript Error Types Table

NameThrown whenTypical example
TypeErrorA value is not the type the operation needsReading a property of undefined; calling a non-function
ReferenceErrorA NAME does not exist at allUsing a variable that was never declared
RangeErrorValue out of legal rangenew Array(-1); Number.toFixed(200)
SyntaxErrorCode cannot parse (or invalid JSON)JSON.parse('{bad}') at runtime
URIErrorMalformed encode/decode inputdecodeURIComponent('%')
AggregateErrorBundle of failuresPromise.any when every input rejects
EvalErrorLegacy - unused by modern enginesKept for spec compatibility
Reference: the MDN Error guide. The diagnostic that saves minutes: TypeError vs ReferenceError - ReferenceError means the name itself was never declared (a typo or a missing import); TypeError means the name exists but the value is not what you assumed (typically undefined). Every error carries .name, .message and .stack; throw accepts anything, but throwing only Error instances preserves stack traces. Bottom line: catch should either handle the specific case or rethrow - swallowing every error turns debugging into archaeology. Related tools: JS promise table (rejections are errors in chains), json formatter (the SyntaxError factory), fetch table for the network layer.

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

  1. Match your console's error name against the table - the Thrown-when column states the broken assumption.
  2. Apply the ReferenceError/TypeError split: undeclared name versus wrong value; it points at the typo or the missing data.
  3. 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.

Related tools