Monday, March 9, 2026
Home TechnologyHow to read stack traces to diagnose problems with your code

How to read stack traces to diagnose problems with your code

by admin7
0 comments


If you’re learning how to write code, then you’ve undoubtedly encountered the dreaded stack trace, and like me, you’ve probably trained yourself to skip reading their garbled output. But they’re not nonsense, and if you take 10 minutes to see the repeating pattern, you can unlock a crucial skill. Solving bugs is an integral part of writing code, so learning to read stack traces is inevitable.

What is a call stack?

A crucial component that records function state and call history

A call stack (aka stack) is a data structure that helps to track the state of functions. It comprises a series of memory addresses (pointers) and stack frames.

A stack frame is like a container. It stores the state for exactly one executing function (variables, etc.). When a function call occurs, a new frame gets placed at the top of the stack, and when the function returns, it’s destroyed.

You can think of a call stack like a vertical column of frames, arranged in call sequence order. It uses pointers to track where each frame lives in memory and the top of the stack.

If you want more details, check out this video from Harvard’s famous CS50 course:

They have dozens of courses on their YouTube channel that you may find interesting.


6 JavaScript Snippets to Polish Your Site

Quick and easy wins for any site you’re building.

How are stack traces organized?

They mirror the order of stack frames

A stack trace presents the call order leading to a failing function. It conveys useful information, such as the file and line number for each function call.

To comprehend a stack trace, the best place to start is the offending frame where the crash occurred. Because stack frames get ordered sequentially, the crashing frame must be the most recent one. Which end of the stack trace that information gets displayed depends on the specific runtime or compiler you used—it’s either at the top or bottom.

Let’s look at some examples and see.

All three of the following stack traces track three function calls (“one,” “two,” and “three”), where “three” crashes the program by dividing by zero.

This is a JavaScript (Node.js) stack trace, and you can see the crashing frame is at the top:

A terminal window displays a Node.js stack trace for a RangeError caused by division by zero. Numbered annotations highlight the crashing line, file path, function name, and line and character numbers.

  • Annotation “1” shows the content of the line that causes the problem.
  • Annotation “2” shows the file where the problem occurred.
  • Annotation “3” shows the function name where the problem occurred.
  • Annotation “4” defines the problematic line and character numbers, respectively.

Notice that there are additional items in the stack trace? The ones grayed out at the bottom represent additional calls that Node.js makes. Everything after “Object” is where our code starts.

You can see from the code below that line 4, character 15, is the division symbol:

A code editor displays a TypeScript source file. An arrow points to the division operator on line 4, inside the three function.

This takes us right to the point of failure and every step leading up to it, which is handy because sometimes the problem is a caller passing invalid values.

Let’s look at Python now:

A terminal window displays a Python stack trace. A ZeroDivisionError is shown, with numbered annotations highlighting the call site and the function where the error occurs.

Notice that the order is reversed? However, the provided information is almost the same, except for one minor difference. Instead of unambiguously stating which function the error occurred in (like Node.js does), Python shows the call site (“1”) and says “in three” (annotation “2”) to show where the error occurred. I find this less readable, but some may not.

Moving on. Let’s look at Golang:

A terminal window displays a Go stack trace. A panic caused by integer division by zero is shown, with the crashing frame highlighted in a box.

A much neater and more uniform output, like the Node.js stack trace. The key difference you should note is the lack of a character number—but you get a line number.

So you see, how a stack trace gets organized depends on what compiler or runtime you’re using. However, some runtimes, compilers, or ecosystems have such disregard for stack traces that they become useless. I’ll show you an extreme example next, hoping that you don’t conflate good and bad stack traces and give up on them entirely.

Some stack traces are unreadable

It’s not just you; they demoralize everyone, but you can sometimes fix them

It’s very easy to forgo reading stack traces and instead describe your problem to Google (now LLMs). This is a behavior long trained into programmers through negligent design. What I mean is, while some languages care about conveying useful information (like Rust or Python), others do not. Common practices in languages like JavaScript make stack traces impossible to read. Bundling, transpiling, and minification are the primary culprits, and every one of these steps removes useful information.

Take the following stack trace, for example. It’s transpiled TypeScript, which is then bundled and minified using Webpack and Terser. It simply means we turned a multi-file TypeScript program into a single chunk of JavaScript and then renamed all the useful function and variable names into single letters.

A terminal window displays a Node.js stack trace from a bundled and minified JavaScript file. Numbered annotations highlight the repeated line number and the error location in the minified code.

Look at annotation “1”; you can see that all occurrences after “Object” are on line “1” because our entire program is on a single line. If you look at annotation “2,” this is where the problem occurs, but it’s still nonsense. The runtime even points to the chunk and says, “Somewhere in there.” These are the typical stack traces you will see in a browser, unless you have source maps configured.

Source maps are separate JSON files that point back to the original source code, referenced by a comment in the transpiled code. When a problem occurs, it’s much easier to find the source of the problem. The browser, for example, even allows you to click through and view the exact TypeScript line. However, they’re generally not used in production code, and they’re often temperamental. The complexity of frontend build systems is another topic entirely, and it suffices to advise you to steer clear of complex setups unless you have complex requirements—so, KISS.

Use LLMs to assist with debugging

Garbage in; solutions out

One great use case for LLMs (like Claude Code) is to read stack traces, even when they’re an unreadable mess like the previous example. Claude would have no problem understanding those if it had access to the source code, too.

To feed Claude a stack trace, you can provide a screenshot or have Claude run the command directly. I’ve been using Claude a lot for writing an Emacs package (because my Emacs knowledge has limits), and it has made Elisp stack traces actually useful and unlocked a major sticking point.

It works well because there is no need for imagination (a weakness of LLMs), and almost everything required to make a diagnosis exists in the trace. Claude (i.e., a computer) can read text orders of magnitude faster than I can, so it plays right into its strengths.

LLMs for debugging work well. However, Claude can still easily get stuck, so it’s not a silver bullet.

A woman coding on her computer with less-than and greater-than symbols around her.


Become a Better Programmer: 7 Habits to Grow

Battle-tested habits to write better programs.


Let’s recap and summarize.

  1. A call stack is a data structure of pointers and stack frames.
  2. A stack frame is an isolated segment of memory used to store function (or method) state.
  3. Stack traces are sequentially ordered and contain the function name, file path, line number, and sometimes character number of every call made leading up to the problem.
  4. The most recent line in the stack trace is where the problem occurs, which may be at the top or bottom (depending on which runtime or compiler you use.)

Start where the problem occurs. Most times, the signal is clear, and the problem is easy to resolve. You may need to look at previous steps if they’re passing corrupt values.

The key takeaways are to look around the point of failure and understand that most of the stack trace information is pointing to locations within the source code.

For frontend code, have a dedicated development build, which includes source maps and skips minification and bundling. Development web servers like Vite take this approach and serve ES modules (plain, readable JS) in development mode but bundle the code for production builds.



Source link

You may also like

Leave a Comment