Read a Python traceback from the bottom up: the last line names the error, and the frame just above it is where it happened ("most recent call last"). Read a JavaScript or Node.js stack trace from the top down: the first line is the error, and the first at line is where it happened. In both, look for the first frame that points at a file you wrote; that is usually the line to fix.
The examples below are real traces, produced by running small scripts with Python 3.12.2 and Node.js 18.18.0 on Windows. Paths are shortened to C:\work\ for readability.
Reading a Python traceback
This script sums the prices in an order, but one item has no qty:
import json
def load_order(text):
order = json.loads(text)
return order
def order_total(order):
return sum(item["price"] * item["qty"] for item in order["items"])
def main():
order = load_order('{"items": [{"price": 4.5, "qty": 2}, {"price": 3}]}')
print(order_total(order))
main()
Running it prints:
Traceback (most recent call last):
File "C:\work\orders.py", line 18, in <module>
main()
File "C:\work\orders.py", line 15, in main
print(order_total(order))
^^^^^^^^^^^^^^^^^^
File "C:\work\orders.py", line 10, in order_total
return sum(item["price"] * item["qty"] for item in order["items"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\work\orders.py", line 10, in <genexpr>
return sum(item["price"] * item["qty"] for item in order["items"])
~~~~^^^^^^^
KeyError: 'qty'
Read it in this order:
- The last line: what went wrong.
KeyError: 'qty': a dictionary was asked for the keyqtyand doesn't have it. The Python tutorial puts it this way: "The last line of the error message indicates what happened." - The frame just above it: where.
File "C:\work\orders.py", line 10, in <genexpr>. The~~~~^^^^^^^markers under the code point atitem["qty"]specifically.<genexpr>is the generator expression insidesum(). - Work upwards for how you got there.
order_totalwas called frommainon line 15, which was called from the module's top level (<module>) on line 18. "Most recent call last" means the oldest call is at the top and the newest at the bottom.
The fix is not necessarily on line 10. The real question is why an item without qty got that far. Either the data is wrong (fix it where it is produced) or a missing quantity is legitimate (use item.get("qty", 1)).
Python: when the error is inside a library
Here the bug is a config string with single quotes, which JSON doesn't allow:
Traceback (most recent call last):
File "C:\work\parse.py", line 8, in <module>
read_config("{'debug': true}")
File "C:\work\parse.py", line 5, in read_config
return json.loads(text)
^^^^^^^^^^^^^^^^
File "C:\Program Files\Python312\Lib\json\__init__.py", line 346, in loads
return _default_decoder.decode(s)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Python312\Lib\json\decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Python312\Lib\json\decoder.py", line 353, in raw_decode
obj, end = self.scan_once(s, idx)
^^^^^^^^^^^^^^^^^^^^^^
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
The bottom three frames are in C:\Program Files\Python312\Lib\json\, Python's standard library. Library code is very rarely the bug. Skip upward past those frames to the first one in your own project: parse.py, line 5, json.loads(text), called from line 8 with the bad string. That is the line to look at.
The path tells you whose code a frame is. Common patterns:
| Path contains | Whose code |
|---|---|
| Your project folder | Yours: start here |
Lib\json\, /usr/lib/python3.x/ | Python's standard library |
site-packages, .venv | Third-party packages (see virtual environments) |
node:internal/... | Node.js internals |
node_modules | Third-party npm packages |
Python: two tracebacks in one error
When one exception is raised while handling another, Python prints both. This version wraps the JSON error in a clearer one with raise ... from (library frames and ^ markers trimmed):
Traceback (most recent call last):
File "C:\work\chain.py", line 10, in read_config
return json.loads(text)
...
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\work\chain.py", line 15, in <module>
read_config("{'debug': true}")
File "C:\work\chain.py", line 12, in read_config
raise ConfigError("settings.json is not valid JSON") from e
ConfigError: settings.json is not valid JSON
The final error is at the very bottom, and the original cause is in the first block. The separator line tells you how they are related. "The above exception was the direct cause of the following exception" appears with raise ... from. "During handling of the above exception, another exception occurred" appears when an except block raises a new exception without from, including by accident when the handler code itself fails. Read the first block to find the root cause.
Reading a Node.js stack trace
A similar bug in JavaScript: the handler passes order.data, which doesn't exist, to a function that expects an object:
function orderTotal(order) {
return order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
function handleRequest(body) {
const order = JSON.parse(body);
return orderTotal(order.data);
}
console.log(handleRequest('{"items": [{"price": 4.5, "qty": 2}]}'));
Node.js prints:
C:\work\orders.js:2
return order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
^
TypeError: Cannot read properties of undefined (reading 'items')
at orderTotal (C:\work\orders.js:2:16)
at handleRequest (C:\work\orders.js:7:10)
at Object.<anonymous> (C:\work\orders.js:10:13)
at Module._compile (node:internal/modules/cjs/loader:1256:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
at Module.load (node:internal/modules/cjs/loader:1119:32)
at Module._load (node:internal/modules/cjs/loader:960:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:86:12)
at node:internal/main/run_main_module:23:47
Node.js v18.18.0
The order is the reverse of Python's:
- The error line. The Node.js docs describe the format as
<error class name>: <error message>. Here:orderwasundefinedwhen the code read.itemsfrom it. - The first
atline: where.orderTotal, fileorders.js, line 2, column 16. Uncaught errors also print that source line at the very top with a^under the exact column. - Read downwards for the callers.
handleRequestcalled it at line 7 withorder.data, which is where theundefinedcame from. The fix belongs there:orderTotal(order). - Ignore the
node:internalframes. They are Node.js loading and running your file, the same in every trace.
Object.<anonymous> is the top level of the file. The docs explain that V8 shows a function name when it can determine one and otherwise shows only the location.
Node.js: built-in functions and missing frames
When the error is thrown by a built-in, the first frame has no file location:
SyntaxError: Unexpected token ' in JSON at position 1
at JSON.parse (<anonymous>)
at readConfig (C:\work\parse.js:2:15)
at Object.<anonymous> (C:\work\parse.js:5:1)
Skip JSON.parse and go to readConfig, line 2, column 15: your code passed a string with single quotes. The Node.js docs warn that error.message strings "may change between any versions of Node.js", so when searching for an error, use the error class and the gist rather than the exact text.
Two limits to know. V8 records 10 frames by default (Error.stackTraceLimit), so deep call chains get cut off; raise it with Error.stackTraceLimit = 50 at startup or run node --stack-trace-limit=50 app.js. And frames are only generated for JavaScript functions, so calls that pass through native addons leave gaps.
A routine that works for any trace
- Find the error type and message (bottom line in Python, top line in JavaScript). Search for the exact message if it's new to you.
- Find the frame where it was raised.
- Move toward the outermost call until you reach the first frame in your own code.
- Open that file at that line and ask what value was wrong, and where it came from one frame further out.
If the trace shows up in a server log after an HTTP 500, it is the same exercise; HTTP status codes explained covers which codes point at the server. For bad JSON specifically, the error message's line and column number point straight at the problem character, and JSON vs YAML vs TOML lists the usual syntax mistakes.
Frequently asked questions
- Why does the error appear in a library file when the bug is in my code?
- The library is where the bad value was finally used, not where it came from. Follow the trace to the last frame that points at one of your own files; that line passed the bad input. In the JSON example in this article, the error is raised inside json/decoder.py, but the cause is the string passed in from read_config.
- What do the ^^^^ and ~~~~ marks under Python traceback lines mean?
- They show which part of the line failed, a feature added in Python 3.11 by PEP 657. The ^ characters mark the failing operation, and ~ marks the surrounding expression. They can be turned off with the PYTHONNODEBUGRANGES environment variable or python -Xno_debug_ranges.
- Why is my Node.js stack trace cut off?
- V8 collects only 10 frames by default. The Node.js docs say Error.stackTraceLimit sets the number, so setting Error.stackTraceLimit = 50 at the start of your program, or running node --stack-trace-limit=50, shows more of the call chain.
- What does 'at Object.<anonymous>' mean in a Node.js trace?
- It is the top level of a CommonJS module: code in the file that is not inside any named function. V8 shows a name when it can work one out, and otherwise shows only the location.