How to Read Errors
← → navigate
Debugging · Exception Handling · Diagnostic Engineering

How to Read
Errors

From cryptic stack traces to structured exception handling — a field guide for every paradigm, every error type, and every level of experience.

⚑ Syntax ⚡ Runtime ⊘ Logic
"An error message tells you exactly what went wrong in your code, and where. Read it carefully and literally."
01
Mental Model
02
Error Anatomy
03
Reading Protocol
04
By Paradigm
05
Exception Lifecycle
06
Debugging Toolkit
07
Design Principles
08
Anti-Patterns
09
Synthesis
10 pages · ← → keys
How to use this guide
Press ← → arrow keys to move between pages. Each page is independently scrollable. Click any chapter above to jump directly.
Page 01

The Mental Model

Errors are diagnostic boundaries — not punishments. Every error message is a collaborative signal designed to navigate you through a failing program's state space.

Errors vs. Warnings

These are not the same. Conflating them causes developers to either panic at non-critical warnings or dismiss real halting errors.

Signal TypeWhat it meansExecution impactVisual convention
ErrorCritical halting state. Data integrity or execution is at risk.Stops execution to prevent systemic collapse🔴 High-contrast red
WarningInformational — logical issue, deprecation, or performance risk.Does not halt execution🟡 Cautionary yellow

The Three Flavors of Error

01 — Syntax

Code can't even start

Typos, missing punctuation, malformed structure. The parser rejects the file before execution begins. Usually points to a specific line.

02 — Runtime

Code starts, then crashes

Happens during execution. Calling something that doesn't exist, accessing null, dividing by zero. Comes with a call stack and line number.

03 — Logic

Code runs, produces wrong output

No error message. The program does the wrong thing silently. Requires reasoning about state, not reading a diagnostic.

The Debug Time Formula

Debugging time is the sum of three distinct sub-tasks. Modern diagnostic tools target each independently.

Total Debugging Time
Tdebug = Tisolation + Tcomprehension + Tremediation
ComponentDefinitionReduced by
T_isolationTime to locate the failing component or lineStack traces, line numbers, breakpoints
T_comprehensionTime to parse and understand the diagnostic outputHuman-centric error messages, dual-binning
T_remediationTime to apply, verify, and deploy the fixClear resolution guidance, automated tests
Key Insight
Poor error message design increases T_comprehension disproportionately. A single well-worded error message that says what failed, why, and how to fix it can collapse all three components simultaneously.

The Historical Shift

Historically, error messages were viewed negatively — signals of failure. Modern developer experience (DX) engineering reframes them as critical diagnostic indicators and tools for learning. Under this paradigm, error states are boundaries that define the logical limits of an application, guiding iterative system refinement.

The dividing line between "compiled" and "interpreted" diagnostics has largely broken down due to modern JIT compilation runtimes and interactive language servers. Modern diagnostics focus on semantic and context-aware analysis, not strict runtime execution boundaries.

Page 02

Error Anatomy

Every error message has structure. Learning to parse that structure is a skill — and it transfers across every language and environment.

Dissecting a Real Error

Take this common JavaScript browser error:

Uncaught TypeError : Cannot read properties of null (reading 'addEventListener')
Uncaught — not wrapped in try/catch
TypeError — the error class
Human-readable description
Specific operation that failed

What this actually tells you: You queried a DOM element that doesn't exist (returned null), then tried to call .addEventListener() on it. The element either has the wrong selector or doesn't exist in the DOM at the time the script runs.

The JavaScript Error Object

In JavaScript, every thrown error is an object with three built-in properties. The stack trace is the most powerful — it maps the entire call chain backward to the root cause.

PropertyTypeWhat it containsExample
namestringThe classification/class of exceptionTypeError
messagestringHuman-readable failure descriptionCannot read properties of null
stackstringArray of execution frames — full call history to the exception pointat init (app.js:14) at window.onload...

JavaScript Built-in Error Classes

Error ClassRoot CauseSystem ImpactCommon Trigger
SyntaxErrorGrammar violations during parsingHalts compile/parse phaseMismatched brackets, malformed JSON, unescaped characters
ReferenceErrorReferencing undeclared or uninitialized variablesHalts active scope executionVariable out of scope, misspelled identifier, var hoisting surprises
TypeErrorIncompatible types, or mutating immutable valuesAborts the active operationCalling methods on null/undefined, invoking non-functions
RangeErrorValues exceeding numeric limitsHalts numerical evaluationInvalid array lengths, maximum recursion depth exceeded
URIErrorInvalid characters in URI encoding functionsHalts string encodingMalformed URL parameter passed to decodeURI()
EvalErrorExceptional conditions in eval()Minimal in modern enginesLegacy API — preserved for backward compatibility

Reading a Stack Trace

Read stack traces top to bottom. The top is where the error was thrown. The bottom is where execution entered. Your code is usually in the middle — filter out framework internals.

// Stack trace example — read top → down
Uncaught TypeError: Cannot set properties of null
    at attachListener (app.js:23)   ← YOUR CODE — error originates here
    at init (app.js:8)              ← YOUR CODE — called attachListener
    at HTMLDocument.onload (app.js:2)  ← entry point
Case Sensitivity Trap
addeventListener vs addEventListener — the engine evaluates the misspelled term as undefined and throws "not a function". The error class (TypeError) tells you the exact failure category before you even look at the message.
Page 03

The Reading Protocol

Four steps. In order. Don't skip ahead. Most debugging failures are caused by jumping to step 3 before completing step 1.

  1. 1

    Read the message fully and literally

    Don't skim. Take each word at face value. "Missing argument" means an argument is missing. "Permission denied" means the process lacks permission. Many errors contain the fix embedded in plain language. If it says "undefined is not a function," something is undefined that you expect to be a function — start there.

  2. 2

    Use context — file, line, stack

    Good diagnostic output includes the filename and line number. Jump to that location immediately. In Python or JS, the traceback shows the call stack — read from the top (the actual error) down (the call chain). The pointed-to line contains the mistake or a direct clue to it. In terminal contexts, run pwd first — path misalignment causes many false leads.

  3. 3

    Search for the error

    Copy the error message into a search engine. Strip application-specific variable names — search for the general symptom, not your specific implementation. "return random item from array javascript" yields more useful results than "how to fix rock paper scissors game". Check official documentation first, then StackOverflow — and read alternative answers on threads, not just the accepted one.

  4. 4

    Ask for help — with a MCRE

    If still blocked: consult colleagues, technical communities, or forums. Construct a Minimal, Complete, Reproducible Example — share only the specific code block containing the bug, not the whole codebase. List the diagnostic steps already taken. Attach screenshots or isolated playgrounds (CodePen, Scrimba). Including the full error message is mandatory.

The Read-Search-Ask Flowchart

flowchart TD A([Error Encountered]) --> B[Read & Localize\nDevTools / Logs / Stack Trace] B --> C{Is the cause\nclear?} C -->|Yes| F[Fix it] C -->|No| D[Search & Index\nDocs → StackOverflow] D --> E{Still\nblocked?} E -->|No| F E -->|Yes| G[Ask Technical Communities\nWith MCRE + steps taken] G --> F F --> H[Fix one thing.\nRe-run. Read next error.] H --> I([Repeat until clean])

Practical Search Technique

Too specific — won't find results
"how to create rock paper scissors game in javascript TypeError"
Symptom-first — findable
"TypeError undefined is not a function javascript DOM"

Error messages are designed to be searchable. Unique phrases and codes lead directly to documentation and community discussions. Copy the exact error text — don't paraphrase it.

The Iteration Rule

Fix one thing at a time, then re-run. One error often hides another beneath it. Each new error is a fresh clue, not evidence that your fix failed. Treat the process as incremental excavation: each solved error is progress.

"You have to read error messages very literally — like a puzzle where every word is a piece."

REPL Introspection (Ruby / Python)

In interactive REPL environments, query object APIs directly to verify method signatures and avoid interface typos:

# Ruby IRB — inspect all available methods on a string
"string_data".methods.sort
# Returns: [:*, :+, :<<, :<=>, :==, :===, :=~, ...] — verifiable API surface
Page 04

By Paradigm

Different programming paradigms use fundamentally different strategies to isolate and propagate failures. Same problem — radically different interfaces.

Paradigm Comparison Matrix

FeatureC / UnixLabVIEW (Visual)GameMaker (Event)JavaScript (Dynamic)
Core Mechanism Integer return codes, errno global flag Graphical error clusters (boolean + code + source string) Event-driven instance traces, uninitialized field alerts Dynamic call-stack exception objects, prototype errors
Primary Interface stderr stream, system loggers Front Panel indicators, broken wire arrows, popups Output Log, show_debug_message(), F6 Debugger Browser DevTools Console, Sources panel, live expressions
Recovery Strategy Manual cleanup via goto to deallocation label Broken wire redirects in block diagram flow Conditional initializers, VM vs YYC compile target Guarded try/catch/finally blocks, custom error bubbling
Syntax vs Logic Split Strict compilation rejection vs. silent memory corruption Broken arrows prevent compilation vs. silent dataflow errors Syntax warnings vs. silent runtime script halts Execution halts on uncaught exceptions vs. implicit type coercion
Runtime Overhead Zero-cost abstractions Low-Moderate (visual buffer rendering) Moderate (frame-by-frame monitoring) High (dynamic stack trace generation)

C / Unix — Systems-Level Procedural

C lacks high-level exceptions. Errors surface as return values — check for -1 or NULL. The errno global is set by failing system calls; strerror() translates it to a human-readable string. Critical: reset errno = 0 before operations to prevent diagnostic pollution from prior failures.

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

void perform_directory_shift(const char *target_path) {
    errno = 0;  // Reset — prevent pollution from prior failures
    if (chdir(target_path) == -1) {
        // __FILE__ and __LINE__ inject precise structural origin
        fprintf(stderr, "[ERROR] %s at line %d: %s\n",
                __FILE__, __LINE__, strerror(errno));
    }
}

LabVIEW — Visual Dataflow

Compilation is continuous. Syntax errors appear as broken execution arrows that physically prevent the program from running. Data flows along wires between functional nodes — errors travel as graphical error clusters.

LabVIEW Debugging Tools
Execution Highlighting — animates data flow with visual bubbles, slowing execution for inspection.
Probes — right-click any wire to insert a probe outputting live values without halting.
Step Controls — step into/over/out of SubVIs to isolate logic in nested structures.

GameMaker — Event-Driven / Time-Sliced

Execution is tied to game loops and frame updates. A variable referenced in a Step Event (runs every frame) crashes the engine if not initialized in the parent object's Create Event. Timing is everything.

Common CauseWhat HappensFix
Uninitialized variables in Step EventEngine crash on first frameInitialize all variables in Create Event
String without quotesCompiler interprets as undeclared variableAlways quote text literals
Child overrides Create EventParent variables never initializeCall event_inherited() in child's Create Event
Compiling with YYC instead of VMLess descriptive runtime diagnosticsUse VM for development, YYC for release

JavaScript — Dynamic Stack

JS errors are standard runtime objects with name, message, and stack properties. The engine's dynamic stack trace generation has high runtime overhead but provides complete call history at the moment of failure.

// Identify error class to narrow failure space
try {
    let rawData = '{"invalid_json": true,';  // Malformed JSON
    let parsed = JSON.parse(rawData);
} catch (error) {
    console.error(`Error Type: ${error.name}`);    // → SyntaxError
    console.error(`Message: ${error.message}`);    // → Unexpected end of JSON input
    console.error(error.stack);                     // → Full call chain
}
Page 05

Exception Lifecycle

In high-level languages, exception handling is a core structural feature — four coordinated blocks that intercept runtime errors and prevent sudden program termination.

The try / catch / else / finally Flow

flowchart TD A([Normal Execution]) --> B[try Block\nDangerous operation isolated here] B --> C{Exception\nraised?} C -->|Yes| D[catch / except Block\nIntercepts & binds error object\nLog context · Perform cleanup] C -->|No| E[else Block\nExecutes ONLY if try completed\n100% exception-free] D --> F[finally Block\nRuns UNCONDITIONALLY\nResource release · Lock cleanup] E --> F F --> G([Resume Execution])

The Four Blocks

try

The Guarded Zone

Isolates code containing potential hazards — I/O operations, database transactions, network calls. When an exception occurs, the runtime halts the try block and unrolls the call stack to find a matching handler.

catch / except

The Intercept

Binds the thrown exception object to a local variable for inspection. Always catch specific exceptions — never use a bare except: or catch-all handler. Bare handlers swallow unrelated issues (memory errors, keyboard interrupts).

else

The Clean Path

Executes only if the try block completed without raising any exception. Useful for code that should run only on success — separates "success logic" from "error logic" cleanly.

finally

The Unconditional Guarantee

Runs regardless of whether an exception occurred, was caught, or is being rethrown. Ideal for releasing system locks, closing DB sessions, flushing buffers, closing file streams.

Python — Specificity and Binding

# Bind exception metadata to inspect failure state
try:
    divisor = float(input("Enter denominator: "))
    result = 100 / divisor
except ZeroDivisionError as error_variable:
    # Captures: "division by zero"
    print(f"Mathematical Exception: {error_variable}")
except ValueError as e:
    # Catches invalid input that can't be converted to float
    print(f"Input Error: {e}")
else:
    # Only runs if no exception occurred
    print(f"Result: {result}")
finally:
    print("Cleanup: this always runs")
Anti-Pattern: Bare except
except: with no type catches everything — including SystemExit, KeyboardInterrupt, and MemoryError. This masks logical flaws and makes debugging significantly harder. Always name the exception type.

JavaScript — Custom throw + Bubbling

// Custom throw + nested try-catch-finally
function processUserInput(input) {
    try {
        if (input === "") {
            throw new Error("Empty input payload");  // Custom throw
        }
        return `Processed: ${input}`;
    } catch (error) {
        console.error(`Fault: ${error.name} — ${error.message}`);
        return null;
    } finally {
        closeActiveConnection();  // Unconditional cleanup
    }
}

Exception Bubbling

In nested execution contexts, exceptions bubble up through scope boundaries. If an inner try block has a finally but no catch, any thrown exception is deferred until finally completes, then bubbles to the nearest outer catch. This enables:

JavaScript Throw Flexibility
JS allows throwing any data type — strings, numbers, booleans, or custom objects. Best practice: always throw instances of the built-in Error constructor (or a subclass) so consumers can access .name, .message, and .stack reliably.
Page 06

Debugging Toolkit

Six methodologies, each with distinct operational trade-offs. Choosing the right tool for the error type cuts debugging time dramatically.

The Six Core Methods

MethodMechanismAdvantagesTrade-offsBest For
Print / Console Logging console.log, show_debug_message, print() Portable, no IDE config, immediate temporal feedback Clutters source, adds I/O overhead, manual removal required Quick state inspection, tracking execution pathways in simple scripts
Code Bisection Comment out ranges of code to locate error triggers No specialized tools needed, effective for compilation errors Can disrupt dependencies, requires systematic restoration Pinpointing syntax violations or blocking loops without a debugger
Interactive Breakpoints Chrome DevTools, VS Code, GDB — line-specific pause targets Inspect lexical scopes, track call stack, modify state variables live Requires IDE integration, difficult in async/distributed systems Isolating logical anomalies, complex recursion, variable value tracing
Static Analysis / Linting ESLint, JSHint — parse source for rule violations Catches errors before execution, enforces coding standards False positives, requires upfront rule configuration Preventing basic syntax errors and uninitialized variable bugs pre-compile
Automated Testing Jest, JUnit — assert code outputs against expected boundaries Catches regressions automatically, ensures reliability over time Significant time to write/maintain, can't catch unpredicted logical flaws Preventing regression bugs, verifying complex computational logic
Peer Review / Rubber Duck Explain code to a peer or inanimate object line-by-line Forces re-evaluation of assumptions, often reveals root cause instantly Dependent on availability, slow for simple syntax resolution Complex logic errors, refactoring legacy code, debugging design flaws

Advanced Console Methods

// Standard — variable at a point in time
console.log(myVariable);

// Tabular — for arrays/objects
console.table(usersArray);

// Full call stack at current point
console.trace("Reached here");

// Grouped output
console.group("Auth Flow");
console.log(tokenResult);
console.groupEnd();
Print vs Debugger
Print statements show a snapshot. A debugger lets you pause time — inspect all variables in scope, step through logic line by line, and modify state live. For anything beyond "what is X at this point," use the debugger.

The Read-Search-Ask Paradigm

flowchart LR A[Phase 1\nRead & Localize\nDevTools · Logs · pwd check] --> B[Phase 2\nSearch & Index\nDocs first · StackOverflow · Alt answers] B --> C[Phase 3\nAsk Technical Communities\nMCRE · Steps taken · Screenshots]

Rubber Duck Debugging

Explaining code line-by-line to an inanimate object forces re-evaluation of assumptions. It works because:

When to escalate to peer review
When self-debugging loops more than 45 minutes without progress, bring in a peer. A fresh perspective spots errors the original author has grown blind to. In professional environments, collaborative debugging is significantly more efficient than isolated isolation.
Page 07

Error Design Principles

How error messages are designed directly determines how fast they can be read. Good design collapses T_comprehension to near zero. Bad design compounds the damage of the original failure.

Alan Cooper's Usability Triad

Every error message is the software confessing it couldn't complete its task. That confession should follow three rules:

Be Polite

Never blame the user

An error box is a confession from the software. It should never imply the user is at fault. Tone affects whether users engage with the diagnostic or dismiss it.

Be Illuminating

Describe the full situation

Scope of the problem, current system state, what alternatives exist, what data was lost. Not just that something failed — what state the system is in now.

Be Helpful

Actively suggest solutions

Don't dump a problem on the user. Offer alternatives within the interface — "use a different printer," "retry in 30 seconds," "check your permissions."

The Dual-Binning Framework

Every error message should separate into exactly two bins. Information that fits neither bin should be pruned entirely.

┌─────────────────────────────────────────────────────────────────┐
│                        ERROR DIAGNOSTIC                         │
├─────────────────────────────────────────────────────────────────┤
│ [ISSUE BIN]                                                     │
│ Cause: Connection failed — API server refused port 443.         │
│                                                                 │
│ [RESOLUTION BIN]                                                │
│ Action: Verify host routing table or run                        │
│         `ping api.service.local` to trace.                      │
└─────────────────────────────────────────────────────────────────┘
29–60%
Drop in browser abandonment when Mozilla Firefox redesigned network error pages to use dual-binning format
3 parts
What occurred + Subject of the action + Target of the action — the minimum for an actionable error message

Subject · Target · Action Pattern

⚑ Vague — not actionable
"Printing failed"
✓ Specific — actionable
"Paper Jam printing document1 to printer2"
⚑ Discards dynamic context
if (result !== success) {
    display("Static error — no context");
}
✓ Preserves runtime metadata
if (result !== success) {
    display(`${result} when printing
${document1.name} to ${printer2.name}`);
}

API Style Guide (Temporal Platform)

Modern frameworks enforce strict error message style rules for uniformity and machine-readability:

RuleWrongRight
Lowercase, no trailing punctuationUnable to open file.unable to open file
Consistent verb choicefailed to... / can't... / could not...unable to... (consistent)
Logical operation namesLoadServiceConfig errorunable to load service config
Expected vs actualwrong valueexpected string, got number: 42

Configuration File Diagnostics

Config errors cause some of the worst debugging experiences — cryptic KeyError: 'keyname' messages without file name, key name, or line number. Good config parsers (like C++'s Configuru) provide surgical precision:

config.json:42: Bad indentation: expected 3 tabs, got 2
config.json:17: Warning: key 'timeout_ms' defined but never accessed

Systems should display full-screen persistent diagnostics rather than burying errors in background log files. These warnings should pause execution safely and automatically reload once file system changes are detected — enabling fast iteration loops.

Page 08

Anti-Patterns

Common structural mistakes that turn simple bugs into multi-hour debugging sessions. Each one corrupts the diagnostic signal.

1. Failing Silently

Swallowing exceptions inside empty catch blocks is the most damaging anti-pattern. Corrupt state propagates unnoticed. The program continues running — wrong.

⚑ Empty catch — silent corruption
try {
    await loadUserData(userId);
} catch (e) {
    // swallowed — nothing happens
}
✓ Log + handle + rethrow if systemic
try {
    await loadUserData(userId);
} catch (e) {
    logger.error('loadUserData failed', {
        userId, error: e.message
    });
    throw e;  // let caller decide recovery
}

2. Bare except / Catch-all Handlers

Using a generic exception trap swallows unrelated runtime issues — memory exhaustion, syntax errors, keyboard interrupts — masking logical flaws.

⚑ Catches everything — masks bugs
try:
    result = risky_operation()
except:  # bare except — catches SIGINT, MemoryError, everything
    pass
✓ Named exception types only
try:
    result = risky_operation()
except ValueError as e:
    handle_value_error(e)
except ConnectionError as e:
    handle_connection_error(e)

3. Abstract Equality Coercion (== vs ===)

JavaScript's abstract equality operator performs implicit type coercion — a common source of TypeError and logic errors.

// Abstract equality — coerces types
5 == "5"     // → true  (string coerced to number)
null == undefined  // → true  (implicit equivalence)
0 == false   // → true  (falsy coercion)

// Strict equality — no coercion
5 === "5"    // → false (different types)
null === undefined  // → false
0 === false  // → false
Rule
Always use === and !== in JavaScript. The only valid use of == is checking for null/undefined together (x == null).

4. Implicit Variable Scope Leakage

var is function-scoped and hoisted — it leaks out of if blocks, for loops, and creates subtle state mutation bugs across different areas of the application.

⚑ var — hoists, leaks out of block
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// Outputs: 3, 3, 3 — not 0, 1, 2
// `i` leaked out of the loop
✓ let — block-scoped, no leak
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// Outputs: 0, 1, 2 — correct
// `i` scoped to each loop iteration

5. Neglecting Cleanup Pathways

Failing to implement finally blocks or explicit resource releases leads to progressive memory leaks, unclosed file descriptors, and locked database pools.

⚑ No cleanup — connection leaks
async function fetchData() {
    const conn = await db.connect();
    try {
        return await conn.query(sql);
    } catch (e) {
        throw e;
        // conn is never closed on error
    }
}
✓ finally guarantees cleanup
async function fetchData() {
    const conn = await db.connect();
    try {
        return await conn.query(sql);
    } catch (e) {
        throw e;
    } finally {
        await conn.close();  // always runs
    }
}

6. Ignoring Linter Warnings

Warning
Linter warnings are pre-runtime error catches. Skipping static code analysis alerts allows easily preventable bugs — implicit globals, type coercion anomalies, unused variables — to slip into production. Each suppressed warning is a deferred debugging session.
Anti-PatternDownstream consequence
Empty catch blocksCorrupt state propagates silently; bugs untraceable at origin
Bare except:Swallows system signals; masks unrelated crashes
== instead of ===Type coercion errors that pass tests but fail in production
var for loop variablesClosure captures wrong value; async/callback bugs
No finally blockResource leaks, locked DB pools, unclosed file descriptors
Ignoring linter warningsKnown bug patterns enter production uncaught
Page 09

Synthesis

The 3-phase resolution protocol, searchable error design, and distributed system tracing — the full picture for systematic fault resolution.

The 3-Phase Resolution Protocol

flowchart TD A([Runtime Exception Triggered]) --> B subgraph B [Phase 1 — Isolation] B1[Read stack dump frames] B2[Match error to specific type\nTypeError · FileNotFound · etc] B1 --> B2 end B --> C subgraph C [Phase 2 — Inspection] C1[Attach debugger probes\nor add targeted logging] C2[Inspect local scope variables\nat failure point] C3[Understand WHY the invalid state occurred] C1 --> C2 --> C3 end C --> D subgraph D [Phase 3 — Verification] D1[Apply fix] D2[Run automated tests\nno regressions introduced] D3[Confirm cleanup blocks release resources\nsystem in clean state] D1 --> D2 --> D3 end D --> E([Resolved])

Searchable Error Design

In modern developer workflows, pasting an error into a search engine is a primary diagnostic step. Error architects must design for searchability.

Unique Alphanumeric Codes

Embedding unique, alphanumeric diagnostic codes in error payloads (e.g., YCOM-HN-9021) provides a searchable keyword that leads directly to documentation. It also enables automated alerts when those strings appear on public indexing sites.

Three Anti-Patterns That Break Search Engines

Anti-PatternWhy It FailsExample
Negative integers Search engines interpret - as an exclusion operator. Searching "Code -4" excludes "4" from results. Error Code -4
Raw hexadecimal arrays Engines strip the 0x prefix or confuse hex numbers with telephone numbers, returning unrelated results. 0x00071153
IBM mainframe taxonomy Highly structured internal codes (module prefix + message number + severity) map to dedicated IBM catalogs — opaque without direct catalog access. IEF403I JOB E
Recommended Pattern
Use alphanumeric prefix codes: CONN-4021, AUTH-E0033, DB-TIMEOUT-12. Human-readable, search-friendly, monitorable.

Distributed Systems — Correlation IDs

Static error codes help with search indexing. Distributed cloud architectures also require dynamic tracing. Modern API gateways inject unique transaction IDs into error headers:

HTTP 503 Service Unavailable
X-Correlation-ID: 7f3a9c2e-8b1d-4f55-a3cc-90e1b2d4f667
X-Error-Code: UPSTREAM-TIMEOUT-03

Service 'auth-gateway' failed to reach 'user-service'
Correlation ID: 7f3a9c2e — use this to locate the exact
microservice interaction in your distributed trace logs.

When a component fails, the correlation ID allows operations teams to locate the exact microservice interaction, container log, or database call in logging tools — collapsing T_isolation in distributed debugging.

Traditional vs Modern Error Design

MetricTraditional PatternsHuman-Centric / Modern Patterns
Syntactic ClarityAbstract or internal-facing codesConversational but precise; follows Cooper's triad
Contextual PayloadDiscards runtime variables; static strings onlyEmbeds dynamic subject, target, and logical action
SearchabilityNegative integers or raw hex — break indexingPrefix-coded alphanumeric strings for easy lookup
System VisibilityShorthand logs easily overlookedHigh-contrast formatting; full-screen pauses for config errors
Recovery FlowHard crashes with empty stack framesStructured try-catch-finally with clean resource release

Key Takeaways

Reading errors
Read fully and literally. Use context — file, line, stack. Search the exact error text. Fix one thing, re-run, repeat. Never paraphrase when copying error text to search.
Writing errors
What + Subject + Target. Issue bin + Resolution bin. Alphanumeric error codes. Preserve dynamic context — never discard runtime metadata in favor of static strings.
Structural discipline
Always name exception types. Always implement finally for resources. Never swallow exceptions silently. Use ===, use let/const, run your linter.
When stuck
Rubber duck first. If 45+ min without progress: peer review. When asking for help: include the full error, the exact steps taken, and a minimal reproducible example. Never paraphrase the error.

How to Read Errors · Built from 3 research documents · 10 pages · ← → to navigate

00 / 09 — Cover