← All articles
🔍 TechniqueBeginner · 7 min read

Reading a Stack Trace: A Working Method

Six small moves that turn the most intimidating part of an error message into a clear pointer at the line you need to change.

By Kishan Vaghani · Reviewed by Kajal Pansuriya · Published May 25, 2026

A stack trace looks like an unfriendly wall of text the first dozen times you see one. It is not. It is a precise instrument that, read properly, points within seconds at the line of code you need to change. The method below is six small moves. None of them are clever; together they make any stack trace — JavaScript, Python, anything — readable.

What a stack trace is actually telling you

When an error is thrown, the runtime captures the chain of function calls that led to the throw and prints them in order from most-recent to least-recent. The top frame is the function that actually threw. Each frame below it is the function that called the one above it. The bottom frame is wherever the program started — a request handler, a button click, a script entry point.

The trace is not the error. The error is the message at the top, usually one short line. The trace is the breadcrumb path the runtime walked to get there. Read the message first; read the trace second.

The six-step routine

Step 1. Read the error message at the top

The first line of the output usually contains everything you need to identify the category of bug: TypeError, ReferenceError, ConnectionRefusedError, ValueError. Read it twice. The category alone tells you whether you are looking at a typo, a logic bug, a missing dependency, or an infrastructure failure.

Step 2. Read the second line — the specific cause

The second line names the specific value that triggered the error. Cannot read properties of undefined (reading 'name') tells you which property was missing. list index out of range tells you the access was past the end of an array. Get the specific value into your head before looking at any frame.

Step 3. Find the first frame in your own code

The top frame is often in a library — node_modules, the React runtime, the Firebase SDK. That is not where the bug lives. The bug lives at the boundary between your code and the library. Scan down the trace until you find the first file path that points at your source files. That frame is the one to read carefully.

TypeError: Cannot read properties of undefined (reading 'id')
    at applyFilters (node_modules/react-query/lib/...)   ← library
    at QueryCache._notify (node_modules/react-query/...) ← library
    at QueryObserver.<anonymous> (node_modules/...)      ← library
    at UserList (src/components/UserList.tsx:42)         ← YOUR FRAME
    at renderWithHooks (node_modules/react-dom/...)
    at mountIndeterminateComponent (...)

In this trace, the bug is at line 42 of UserList.tsx. The frames above it are how the library got there; the frames below are how React got to your component. You only need to look at line 42.

Step 4. Open the named file at the named line

Open the file. Look at the exact line. Find the value that the error message named. In our example, line 42 of UserList.tsx is reading .id off something that turned out to be undefined. The next question is: where did that something come from?

Step 5. Walk upstream from the failing line

The failing line is the point of impact, not the source. Look at where the variable was assigned. Is it the result of an array access that might have returned undefined? Is it a prop that the parent component might have failed to pass? Is it a fetch response that might have come back empty? Walk one step upstream at a time until you find the place where the value first became wrong.

Step 6. Reproduce, then fix one thing

Before changing code, reproduce the failure. Run the exact path that triggered the trace and confirm the same error appears. Then change exactly one thing — add a null check, fix the upstream assignment, correct the prop type — and run the same path again. If the error is gone, you fixed it. If a different error appears, you have made progress: a new error usually means the original bug is fixed and a downstream bug has been exposed.

Worked example

Here is the same routine applied to a real-shaped trace from a Python service:

Traceback (most recent call last):
  File "/app/server.py", line 88, in handle_request
    return self.process(payload)
  File "/app/service.py", line 41, in process
    user = self.users[payload["user_id"]]
KeyError: 'user_id'
  1. Step 1. KeyError — a dictionary lookup failed. This is a logic bug, not a typo or infrastructure issue.
  2. Step 2. Specific value: 'user_id'. The key being looked up was user_id and it was not present in whatever dictionary this is.
  3. Step 3. First frame in our code: line 41 of service.py. Both frames are ours here, so the boundary is thepayload["user_id"] access.
  4. Step 4. Line 41 reads user_id from payload. payload is missing that key.
  5. Step 5. Walk upstream. payload comes from the request body in handle_request. The bug is either a client sending the wrong field name (validation issue) or our code expecting a field that the API contract does not require (assumption issue).
  6. Step 6. Reproduce by sending a request without user_id, then fix one thing — add validation at the request boundary that rejects the request with a clear 400 response instead of crashing with a KeyError.

The habit that compounds

The first dozen stack traces you read carefully will feel slow. The next hundred will feel automatic. After a year of running this routine on every error you encounter, you will be reading traces faster than the runtime can finish printing them, and the intimidation factor will be gone for good. The discipline that gets you there is small but non-negotiable: never paste a trace into a search engine before you have read it yourself.

Related reading

Reading stack traces is one of the compounding skills covered in the engineering-foundations topic. For the broader JavaScript debugging toolkit, the JavaScript debugging guide covers conditional breakpoints, async stack traces, and the DevTools moves that go beyond reading the trace itself.

About the writers

Author

Kishan Vaghani

Founder & Lead Engineer, ShareCode

Founder of ShareCode. Writes the engineering deep-dives on this site — WebRTC, Firebase Auth, real-time sync, and the production patterns behind the editor itself.

Real-time collaboration & CRDTsWebRTC & low-latency mediaFirebase authentication & security rulesNext.js & full-stack JavaScript
Reviewed by

Kajal Pansuriya

Developer Educator, ShareCode

Developer educator at ShareCode. Writes the tutorial track — Python, JavaScript debugging, coding-interview prep, and the everyday code-quality habits that hold up in real codebases.

Python fundamentals & teaching beginnersJavaScript debugging & DevToolsCoding-interview preparationClean code & code review

Stuck on a trace you can't crack?

Paste the trace and the surrounding code into a code space, share the link with a colleague, and walk through the six-step routine together. Most stuck traces unstick within the first three steps when a second reader is in the document.

Open a code space