What a stack trace contains
A stack trace is the string in the error.stack property that holds the error type, its message and the chain of function calls that led to the failure. Each at ... line shows one function, its file, line and column.
Theory
TL;DR
- The call stack is the structure where the engine keeps the list of functions currently running.
- When an error is created, the engine takes a snapshot of that stack and stores it in
error.stack. - The first line of
stackisName: message, followed byat function (file:line:column)lines. - Reading order: the failure site on top, its callers below.
- Asynchronous calls (
setTimeout, promises,await) can break the stack, because they run in a later iteration of the event loop.
Quick example
function a() {
b();
}
function b() {
c();
}
function c() {
throw new Error("Something went wrong!");
}
try {
a();
} catch (err) {
console.log(err.stack);
}Console output:
Error: Something went wrong!
at c (<anonymous>:8:9)
at b (<anonymous>:4:3)
at a (<anonymous>:1:3)
at <anonymous>:12:3What the call stack is
The call stack is a data structure in which JavaScript keeps track of which functions are currently running and in what order they were called.
When an error occurs, the engine takes a snapshot of that stack, the stack trace, which shows where exactly and in what sequence the code that led to the error was called.
How to read stack trace lines
Every line in stack is one call level. Taking the example above apart:
Error: Something went wrong! <- error type and message
at c (<anonymous>:8:9) <- function c() threw the error
at b (<anonymous>:4:3) <- c() was called from b()
at a (<anonymous>:1:3) <- b() was called from a()
at <anonymous>:12:3 <- a() was called in the global scopeEach line gives you:
- the function name (
at c,at b,at a); - the file (or
<anonymous>when the code has no file name); - the line and column number where the call happened.
Where the stack lives: the .stack property
When an error object is created (new Error()), it automatically gets a .stack property that contains:
- the error name (
Error,TypeErrorand so on); - the message (
message); - the stack trace itself.
const err = new Error("Network failure");
console.log(err.stack);Output:
Error: Network failure
at <anonymous>:1:13In real error handling it is convenient to log both parts separately:
try {
throw new Error("Unexpected failure");
} catch (e) {
console.error("Error:", e.message);
console.error("Call stack:\n", e.stack);
}Custom errors have a stack too. If you build your own class with class ... extends Error, the .stack property is available exactly the same way:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
try {
throw new ValidationError("Invalid email");
} catch (e) {
console.log(e.stack);
}Output:
ValidationError: Invalid email
at <anonymous>:8:9
at ...The stack for asynchronous errors
Asynchronous calls (through setTimeout, Promise, await) can break the stack, because they run later, in a different iteration of the event loop.
function a() {
setTimeout(() => {
throw new Error("Timer failure");
}, 0);
}
a();Output:
Uncaught Error: Timer failure
at Timeout._onTimeout (<anonymous>:3:11)Only part of the stack is visible here, because the error happened in a different execution context. There is no
a()in the trace: by the time the timer fired, its frame had already left the stack.
Practical use
A stack trace lets you:
- find the failure site in the code quickly;
- understand which functions led to the error;
- store the trace in logs and monitoring systems (Sentry, GlitchTip, Logtail);
- build your own backend for analysing user-facing errors.
The parts of an error in one place:
| Element | What it means |
|---|---|
Error.message | The error text |
Error.name | The error type (TypeError, ReferenceError, ...) |
Error.stack | The full call path down to the failure site |
Every at ... line | One function in the call chain |
| Order | The failure site on top, its caller below |
Common mistakes
- Treating
error.stackas a standard with a guaranteed format. It is a de facto convention, and the text differs between engines, so parsing it is unreliable. - Thinking the stack is captured at
throwtime. It is actually recorded atnew Error(...), so creating an error early and throwing it later is a bad idea. - Logging only
e.messageand losinge.stack, which leaves nothing to trace the failure with. - Throwing a string or a plain object instead of an
Error, because such a value has no stack at all. - Being surprised by short stacks in asynchronous code. Pass the original error along through
causeso the context is not lost.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.