# What an Error Message Is Actually Telling You

> An error message is not the computer scolding you - it's a precise report of what went wrong and where. This guide teaches you to read its anatomy, recognize the common families, and work through one calmly.


---

# What an Error Message Is Actually Telling You

Here's the moment this guide is for: you run your code, and instead of working, the screen fills with
red text. Your stomach drops. The words look like an accusation, or a wall of nonsense, and the instinct
is to either retype the command and hope, or paste the whole thing somewhere and beg.

Stop. That red text is the single most helpful thing you'll see all day. An error message is the computer
telling you - usually with surprising precision - *exactly* what it tried to do, where it gave up, and
often why. The people who look like wizards aren't smarter than you; they just learned to **read the
message instead of fearing it.** That's a learnable skill, and it's the "A" of debugging. By the end of
this guide, errors will feel less like a slammed door and more like a note left on the fridge.

## How to read this

- **Mid-meltdown right now?** Jump to the cheat-card at the top of
  [Phase 3: What to Actually Do With One](03-what-to-do.md) - symptom on the left, your first move on the
  right.
- **Want it to finally make sense?** Read in order. Each phase builds on the last: first *what an error
  is*, then *the families you'll keep meeting*, then *the calm method* for working through any of them.

## The phases

1. **[An Error Is Information, Not an Insult](01-information-not-insult.md)** - the reframe, plus the
   anatomy of a typical error: its *type*, its *message*, and its *location*. Annotated, in two languages.
2. **[The Common Error Families](02-common-families.md)** - the handful you'll meet constantly: syntax vs
   runtime, null/undefined, type mismatches, not-found, and permission denied. What each usually means.
3. **[What to Actually Do With One](03-what-to-do.md)** - the calm method: read it literally, find *your*
   line, reproduce it small, search the *exact* message, and when to rubber-duck. With a cheat-card.

> This guide is about reading a *single* error. When one error comes with a long, multi-line trail of file
> names and line numbers, that's a **stack trace** - its own skill, covered in
> [Reading a Stack Trace](/guides/reading-a-stack-trace).


---

# An Error Is Information, Not an Insult

The first thing to unlearn is the feeling. When red text appears, it *feels* like the computer caught you
doing something stupid. It isn't - it's a machine that hit a step it couldn't complete, reporting back:
"I got this far, then I couldn't continue, and here's the reason." A status update from a very literal
coworker.

Fear makes you skim - you glance at the red, panic, and miss the line that hands you the answer. Calm makes
you read, and errors turn out to be far more structured than they first appear.

## What an error actually is

An error (also called an *exception*, *fault*, or *traceback*) is a message your language or program
prints when it can't do what you asked: a word it couldn't understand, a thing that was missing, an action
it wasn't allowed to take. Rather than guess what you meant, it stops and tells you.

📝 **Terminology.** *Exception* is the word many languages use for "an error that interrupts normal
running." *Traceback* (or *stack trace*) is the list of steps the program was mid-way through when it
failed. Treat them as flavors of the same thing: a report of a failure.

Beginners read an error as a verdict - "I'm bad at this" - instead of data describing the *code's*
situation, not their worth. Every minute you don't panic is a minute you spend reading, and the answer is
almost always sitting right there in the text.

## The anatomy: three parts in almost every error

Errors are readable because they nearly all carry the same three pieces of information. Spot them and any
error stops being a wall and becomes a form you can fill in.

```text
   ┌─────────────────────────────────────────────────────────────┐
   │  1. WHERE it happened      file name + line number            │
   │  2. WHAT TYPE of problem   the error's name/category          │
   │  3. THE MESSAGE            a sentence describing the specifics │
   └─────────────────────────────────────────────────────────────┘
```

1. **The location** - *which file, which line.* The most valuable part, and the one beginners most often
   skip.
2. **The type** - *the category*, like `TypeError`, `SyntaxError`, or `FileNotFoundError`. Narrows your
   search enormously.
3. **The message** - *a human-readable sentence* with the specifics: which name was undefined, which file
   wasn't found, which value was the wrong type.

## A real example, in Python

Say you have a tiny script that tries to add a number to a piece of text:

```console
$ python greet.py
Traceback (most recent call last):
  File "greet.py", line 3, in <module>
    total = "Age: " + 30
            ~~~~~~~~~^~~~
TypeError: can only concatenate str (not "int") to str
```

*What just happened:* Read it from the **bottom up** - that's where the answer almost always is.

- **The type:** `TypeError` - you used a value of the wrong type.
- **The message:** `can only concatenate str (not "int") to str`. You tried to glue a number (`int`) onto a
  string (`str`) with `+`, and Python only glues strings to strings. (📝 *concatenate* means "join end to
  end.")
- **The location:** `File "greet.py", line 3` - it even underlines the exact expression, `"Age: " + 30`,
  with `~~~^~~` so you don't have to hunt.

Without knowing anything else, the error told you: *line 3, you mixed text and a number, turn the number
into text.* The fix writes itself: `"Age: " + str(30)`.

⚠️ **Read errors bottom-to-top.** Python prints the *path it took* first and the *actual failure* last. The
bottom line (type + message) is the headline; the lines above are the trail. New readers start at the top,
drown in file paths, and miss the line that matters. Start at the bottom.

## The same anatomy, in JavaScript

Same idea, different language - the parts don't change, only the formatting:

```console
$ node cart.js
/home/you/shop/cart.js:5
  console.log(cart.total);
                   ^

TypeError: Cannot read properties of undefined (reading 'total')
    at calculate (/home/you/shop/cart.js:5:20)
    at Object.<anonymous> (/home/you/shop/cart.js:9:1)
```

*What just happened:* All three parts are here, just arranged differently:

- **The type:** `TypeError` again - same category, different language.
- **The message:** `Cannot read properties of undefined (reading 'total')` - you tried to read `.total`
  from something that was `undefined`, nothing was there. (This "nothing where I expected something"
  family returns in [Phase 2](02-common-families.md); it's one of the most common errors in programming.)
- **The location:** `cart.js:5:20` - **line 5, column 20**, with `^` pointing at `cart.total`. The `at ...`
  lines are the stack trace; reading long ones is its own skill in
  [Reading a Stack Trace](/guides/reading-a-stack-trace).

💡 **Key point.** Different languages dress their errors differently, but the same three questions are
always being answered: *where, what type, and what specifically?* Train your eye on those three and you
can read an error in a language you've never used before.

## What about errors with no line number?

Not every error points at a line of *your* code - that's information too. A command-line tool might just
print:

```console
$ npm start
sh: vite: command not found
```

*What just happened:* No file and no line, because the problem isn't *inside* your code - a program
(`vite`) the project expects couldn't be found on your system. The same instinct still helps: type
("command not found"), message (`vite`), rough location (during `npm start`). A missing line number itself
signals the trouble is in your *environment*, not your *logic*.

## Recap

1. An error is a **status report**, not a judgment - a step the computer couldn't finish, reported back.
2. Almost every error answers three questions: **where** (file + line), **what type** (the category), and
   **what specifically** (the message).
3. **Read bottom-to-top** in languages like Python: the last line is the headline, the lines above the trail.
4. The anatomy is **the same across languages** - only the formatting changes.
5. **No line number** usually means the problem is in your environment (a missing tool, a bad path), not
   your code's logic.

Next: the handful of error *families* you'll meet over and over, so reading the type already tells you half
of what went wrong.


---

# The Common Error Families

You're not facing infinite different errors - just a small number of *families*, dressed in different words
each time. Recognize the family from the type and message, and you already know roughly what went wrong
and where to look, like a doctor hearing "sharp pain when you breathe in" pointing to a category before the
exact diagnosis.

## The first fork: syntax errors vs runtime errors

One big split tells you *when* things went wrong, which changes how you hunt.

**Syntax errors - "I can't even read this."** You wrote something the language *cannot parse* - a missing
bracket, a stray comma, a misspelled keyword. The grammar didn't make sense, so it gave up **before running
a single line.** Nothing runs. The *friendly* kind of failure: caught instantly, almost always a small typo
near the spot it names.

```console
$ python app.py
  File "app.py", line 4
    if user == "admin"
                      ^
SyntaxError: expected ':'
```

*What just happened:* Python expected a colon after the `if` condition, didn't find one, and refused to go
further - `^` points at where it gave up. Fix: add the `:` - `if user == "admin":`. It never *ran* your
program; it couldn't get past reading it.

**Runtime errors - "I read it fine, but then something went wrong while doing it."** Grammar valid, so it
started executing - then hit something impossible partway through: a missing value, a missing file, a
number divided by zero. It starts, does some work, then crashes at the bad moment; the output before the
crash shows how far it got.

```console
$ python app.py
Starting up...
Traceback (most recent call last):
  File "app.py", line 8, in <module>
    average = total / count
              ~~~~~~^~~~~~~
ZeroDivisionError: division by zero
```

*What just happened:* The code ran fine, even printing `Starting up...`, until line 8 divided by `count`,
which was `0` this time - and dividing by zero is undefined. The crash is about the *data* it met along the
way, not the grammar.

💡 **Key point.** Syntax error = couldn't *read* your code (nothing ran; look for a typo near the named
line). Runtime error = read fine, went wrong *while running* (look at the data and conditions at that
line). This tells you whether you're hunting a typo or a bad value.

The runtime families you'll meet most.

## Family 1: null / undefined - "nothing where I expected something"

The most common runtime error in the world. You asked for something - a property, a value, an item - and
what was actually there was *nothing*: `null`, `None`, `undefined`, `nil`, depending on the language. You
used that nothing as if it were real: expected a user object, but the variable held nothing, and reaching
for `user.name` crashed because nothing has no `.name`.

```console
TypeError: Cannot read properties of null (reading 'name')
```
```console
AttributeError: 'NoneType' object has no attribute 'name'
```

*What just happened:* Both lines (JavaScript, then Python) say the same thing: the value you reached into
was empty. The fix is rarely at the crash line - it's *upstream*: figure out **why** the value was empty
(a failed lookup? a function returning nothing? data that never arrived?).

⚠️ **The crash location is the symptom, not the cause.** Null/undefined errors point at where you *used*
the emptiness, but the real bug is wherever the value *became* empty. Ask "why was this nothing?" rather
than just guarding the crash line.

## Family 2: type mismatches - "that's the wrong kind of thing"

You gave an operation a value of a type it can't work with: adding a number to text, calling something
that isn't a function, indexing into something that isn't a list. Often the data arrived in an unexpected
shape - like a number that came in as text from a form or file.

```console
$ node total.js
TypeError: amount.toFixed is not a function
```

*What just happened:* `.toFixed()` is a method *numbers* have, but `amount` was probably a string like
`"19.99"`, and strings don't have `.toFixed`. `is not a function` is the classic tell you called a method
on a value that doesn't have it. Fix: convert the value to the right type first.

📝 **Terminology.** A *type* is the kind of value something is - number, string, list, boolean. Type
mismatches are the computer enforcing that you don't accidentally treat one kind as another.

## Family 3: not-found - "I looked for it and it isn't there"

You referred to something by name or path, and it doesn't exist where you pointed. "Something" can be a
*file*, a *module/package*, a *key*, a *variable*, a *column* - anything addressed by name. Usually the
cause is one of three things: a typo, a wrong path or working directory, or a thing you forgot to
create/install.

```console
$ python report.py
FileNotFoundError: [Errno 2] No such file or directory: 'data/sales.csv'
```

*What just happened:* The program tried to open `data/sales.csv` and there's no such file *at the place it
looked*. Often the file exists but you're running from a different folder, so the relative path points
somewhere empty. First check: does the file exist, and are you in the directory you expect? (`ls` / `dir`
shows what's actually around you.)

Same family, different costume - a missing *package*:

```console
$ python app.py
ModuleNotFoundError: No module named 'requests'
```

*What just happened:* Your code said `import requests`, but the package isn't installed. Same family,
different fix: `pip install requests`. One more flavor, a missing *key*:

```console
KeyError: 'email'
```

*What just happened:* You asked a dictionary for key `'email'`, and there's no such key - maybe the data
lacked an email, or it's spelled `'e-mail'` or `'Email'`. Same family: *named thing, not present.*

⚠️ **"Not found" almost always means one of: typo, wrong location, or not-yet-created.** Run through those
three before assuming anything deeper is wrong - nine times out of ten it's one of them.

## Family 4: permission denied - "you're not allowed to do that"

The thing *exists* and your request *makes sense* - but the OS (or a service) refused to let you do it:
write to that folder, open that file, bind to that port, access that resource. Common on shared machines,
system folders, protected files, or reserved ports.

```console
$ ./deploy.sh
bash: ./deploy.sh: Permission denied
```

*What just happened:* `deploy.sh` is there and the path is right - but it isn't marked *executable*, so it
won't run. A *permissions* problem, not a *not-found* one. (Fix: `chmod +x deploy.sh` - the real skill is
knowing "Permission denied" is a *rights* issue, not a typo.)

💡 **Key point.** "Permission denied" differs fundamentally from "not found." Not-found means *the thing
isn't there*; permission-denied means *it's there but you can't touch it that way.* Knowing which one you
got saves you hunting for a missing file when the real issue is access.

## How the families connect to the anatomy

The **error type** (from [Phase 1](01-information-not-insult.md)) told you the family; the **message** told
you the specifics: `FileNotFoundError` → not-found → "which file? where am I running from?"
`TypeError: ... not a function` → type-mismatch → "what type is this really?" Type names the family,
message names the case.

## Recap

1. **First fork:** syntax error (couldn't *read* your code - nothing ran, look for a typo) vs runtime error
   (ran fine, then hit bad data or a missing thing).
2. **Null/undefined** - nothing where you expected something; the cause is *upstream* of the crash line.
3. **Type mismatch** - the wrong *kind* of value for the operation; usually data arrived in an unexpected
   shape.
4. **Not-found** - a named thing (file, module, key, variable) isn't there; check typo, location,
   not-yet-created.
5. **Permission denied** - the thing exists but you're not *allowed*; an access problem, not a missing one.
6. The **type names the family; the message names the case.** Recognize the family first.

Now you can read an error and recognize its anatomy and its family. The last phase covers the calm,
repeatable *method* for resolving one.


---

# What to Actually Do With One

You can now read an error's anatomy and name its family. This phase is the part you'll use every day: a
calm, repeatable *method* experienced engineers run almost without thinking. "Stuck for an hour" vs. "fixed
in five minutes" is almost never raw skill - it's having a method instead of flailing.

## The cheat-card

> **Match what you're feeling or seeing to the row, then read the section under it.**

| The situation | Your first move |
|---|---|
| Wall of red text, panic rising | Find the **bottom line** (Python) or the **type + message** line - that's the headline (§1) |
| You read it but it's still gibberish | Translate it into plain English, one piece at a time (§1) |
| You don't know *where* in your code it is | Find the **first line that names a file you wrote** (§2) |
| The error only happens "sometimes" | **Reproduce it small** - shrink it to the minimum that triggers it (§3) |
| You understand it but don't know the fix | **Search the *exact* message** (minus your personal bits) (§4) |
| Search results are a sea of unrelated posts | Trim the query to the stable core of the message (§4) |
| You've stared for 20 minutes and you're stuck | **Rubber-duck it** - explain it out loud, line by line (§5) |
| The error is a long multi-line trace | That's a stack trace - see [Reading a Stack Trace](/guides/reading-a-stack-trace) |

The rest of this phase is the method behind those moves, in the order you'd run them.

## 1. Read it literally - the top line first

The error means *exactly* what it says. Your first job is to read the headline (type + message; the
**bottom** line in a Python traceback, per [Phase 1](01-information-not-insult.md)) and translate it into a
plain sentence, word by word.

```console
TypeError: Cannot read properties of undefined (reading 'price')
```

*What just happened:* Read it literally: *"I cannot read the property `price`, because the thing I tried to
read it from was `undefined`."* That tells the whole story - something you expected to be an object was
empty, and you reached for `.price` on the emptiness. The error wasn't cryptic; you read it as a blur
instead of a sentence.

💡 **Key point.** Treat the message as English, not noise. Read each word and ask "what does *this word*
mean here?" Half of all "I have no idea what this means" moments dissolve the instant you read slowly
instead of glancing.

## 2. Find *your* line

The error often lists several files - many belong to libraries you didn't write. The line that matters
most is usually the **first one that points at a file you actually created.**

```console
Traceback (most recent call last):
  File "/usr/lib/python3.11/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.11/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=...)
  File "checkout.py", line 12, in <module>
    data = json.loads(response)
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
```

*What just happened:* The top frames are *inside Python's own `json` library* - fine code, don't edit it.
The frame that's **yours** is `checkout.py, line 12`, where *you* called `json.loads(response)`; that's
where your investigation starts. `response` apparently wasn't valid JSON (it expected a value at the very
first character and found nothing) - the library is just reporting the problem your data caused.

⚠️ **Don't start debugging inside library code.** Beginners see the topmost file and try to fix code
they've never seen. Almost always the bug is at *your* line, feeding bad input into otherwise-correct code.
Scan down for the first file with *your* project's name. (For long, tangled lists, that's the skill in
[Reading a Stack Trace](/guides/reading-a-stack-trace).)

## 3. Reproduce it small

An error you can trigger *on demand* is one you can fix. One that happens "randomly" is one you don't
understand *yet*. Before fixing, make it happen reliably and as *small* as possible: comment out unrelated
code, replace live data with a hard-coded value that triggers it, shrink a 200-line script to the 5 lines
that still break. Each thing you remove that *doesn't* stop the error is ruled out.

```console
$ python -c "print('Age: ' + 30)"
TypeError: can only concatenate str (not "int") to str
```

*What just happened:* Instead of re-running a giant program to study one error, you reproduced the exact
failure in a single line at the terminal (`python -c` runs the code you pass it). Now you can poke at it
freely - try `str(30)`, see it work - without the noise of everything else. Shrinking a problem to its
broken part is the core move of all debugging.

💡 **Key point.** "Reproduce it small" does double duty: it confirms you understand what triggers the
error, and gives you a fast, isolated place to test your fix. If you can't reproduce it, you can't be sure
you've fixed it - only hope.

## 4. Search the *exact* message - and read the results

Most errors you'll hit, thousands of people have hit before you. Searching is a real skill, not cheating -
*how* you search decides gold or garbage.

**Search the stable, generic part of the message - strip your personal bits.** The error names *your* file,
variable, path - unique to you, and it'll wreck the search. Cut them, keep what's the same for everyone.

```text
   What you got:
     FileNotFoundError: [Errno 2] No such file or directory: 'data/sales_2026.csv'

   What to search (strip YOUR specifics):
     python FileNotFoundError [Errno 2] No such file or directory
                              └──── keep the stable core ────┘
     ✗ don't include  'data/sales_2026.csv'   ← that's yours; nobody else has it
```

*What just happened:* You kept the language (`python`) and the universal part, dropping your private file
name. Now you're searching for the *category* of problem - what other people wrote about, not your one
file, which no page on earth mentions.

**How to read a search result** - be a skeptic, in this order:

- **Match the error first** - does the result's text match yours, including the type? A similar-looking
  message from a different type is a different problem.
- **Check it's your language/tool and roughly your version** - a fix for an old version, or a different
  language using the same word, sends you down a rabbit hole.
- **Read the *accepted*/most-upvoted answer and *why* it works**, not just the top code block - an answer
  you understand teaches you; one you blindly paste breaks again next week.
- **Be wary of "just add this flag" with no explanation** - if it doesn't say *why*, you can't tell if it
  addresses your cause or just silences the symptom.

⚠️ **Don't paste fixes you don't understand.** The fastest way to turn one bug into three is copying a
snippet that "made the error go away" without knowing why. If you can't explain why it works, you haven't
fixed it - you've hidden it.

## 5. When to rubber-duck

If you've read it literally, found your line, reproduced it, and searched - and you're *still* stuck - you've
likely stopped *thinking* and started staring. The cure is almost silly, and it genuinely works.

📝 **Terminology.** *Rubber-duck debugging* is explaining your problem, out loud and in full detail, to an
inanimate object (classically a rubber duck on your desk). Narrating every step makes the gap obvious.

**Why it works.** Explaining your code *line by line, out loud,* you can't skip the step your brain was
silently assuming was fine. Saying "...and then this returns the user, and then I read the name from the
user..." you hear yourself and stop: *wait - does it actually return the user? What if the lookup found
nobody?* You located the bug by stating what you'd been taking for granted. A real person works too, but
the duck has no ego and is always free.

🪖 **War story.** Nearly every engineer has walked over to a teammate, started explaining an error from the
top, and stopped mid-sentence with "...oh. Never mind. I see it." That's rubber-ducking with a human, and
the teammate didn't say a word. Not embarrassing - the method working.

## The whole method, in order

```text
   1. READ it literally        → what does the headline actually say, word by word?
   2. FIND your line           → first file YOU wrote in the list
   3. REPRODUCE it small       → shrink to the minimum that still breaks
   4. SEARCH the exact message → strip your specifics; read results skeptically
   5. RUBBER-DUCK              → explain it out loud, line by line, until the gap shows
```

Run them in order and most errors fall in minutes. *Fixing* isn't even a separate step - by the time you've
truly read, located, reproduced, and understood the error, the fix is usually obvious. The work was never
the fix - it was the understanding.

## Recap

1. **Read it literally** - the headline means exactly what it says; translate word by word.
2. **Find your line** - the first file *you* wrote, not library code.
3. **Reproduce it small** - make it happen on demand, with the least code possible.
4. **Search the exact message** - strip personal bits, keep the stable core, read results skeptically, never
   paste a fix you can't explain.
5. **Rubber-duck** - explain it out loud line by line; the gap reveals itself.

You now have the full "A" of debugging: errors are information, they come in a few recognizable families,
and there's a calm method for any of them. Next: the **stack trace** - the long, multi-line version of an
error, same skill scaled up.

**Where to go next.**
- **[Reading a Stack Trace](/guides/reading-a-stack-trace)** - when an error comes with a whole trail of
  files and line numbers, how to read it to the real cause.
- **[Reading Logs Without Drowning](/guides/reading-logs-without-drowning)** - when the error is buried in
  thousands of lines of output, how to find the signal in the noise.
