Reading tooling type errors
The 14-line error message, decoded. Where to look first when overwhelmed.
You change one line. The build fails with fourteen lines of error output, six of which are paths into node_modules. You skim, find nothing recognizable, and start guessing. There's a better way to read these. The compiler is being precise — it's just verbose. Once you know where to look first and what each part of the message is for, the long errors stop being scary.
This lesson is the strategy for reading them.
Anatomy of a type error
Every type error has the same shape:
src/checkout.ts:14:21 - error TS2345: Argument of type
'{ id: string; total: number; }' is not assignable to parameter
of type 'Order'.
Property 'currency' is missing in type
'{ id: string; total: number; }' but required in type 'Order'.
14 processOrder({ id: order.id, total: order.amount })
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Five parts:
- Location.
src/checkout.ts:14:21— file, line, column. The actual offending expression. - Code.
TS2345— a stable identifier for this kind of error. Searchable. The same code always means the same thing. - Headline. "Argument of type X is not assignable to parameter of type Y." The one-line summary.
- Reason. Indented below the headline. "Property
currencyis missing." This is the why. - Pointer. The squiggle under the bad expression. Tells you exactly which characters the compiler objects to.
Read in this order: 1 → 5 → 3 → 4. Get the location, look at the squiggled expression in your file, then read the headline, then read the reason. The reason is usually the answer.
When an error feels overwhelming, ignore the headline and read the indented reason first. The indented part is the specific thing the compiler is saying about your specific code.
Reading the assignability trace
When the types are nested, the error grows a trace — a chain of "and inside that, this part doesn't match either."
error TS2322: Type '{ items: { sku: string; qty: string; }[]; }' is not
assignable to type 'Cart'.
Types of property 'items' are incompatible.
Type '{ sku: string; qty: string; }[]' is not assignable to type
'CartItem[]'.
Type '{ sku: string; qty: string; }' is not assignable to type
'CartItem'.
Types of property 'qty' are incompatible.
Type 'string' is not assignable to type 'number'.That looks like a wall of text. It's actually a clean drill-down. Each indent level descends one structural level into the type.
- Top: the whole object doesn't match
Cart. - One level in: the
itemsproperty is the problem. - Two levels in: each item doesn't match
CartItem. - Three levels in: the
qtyfield is the problem. - Bottom:
qtyisstringbut should benumber.
Read from the bottom up. The leaf of the trace is the actual mismatch. Everything above it is the path through your types to get there. Once you fix qty, the whole chain collapses.
/* before — qty was a string */
const cart = { items: [{ sku: "a", qty: "2" }] };
/* after — qty is a number */
const cart = { items: [{ sku: "a", qty: 2 }] };That single change makes the entire fourteen-line error vanish. Long errors usually have a small fix — the chain just describes how deep the mismatch is buried.
After the main error, the compiler often appends a "Related" line that points to a different file — usually where the offending type is defined.
error TS2345: Argument of type 'string' is not assignable to parameter
of type 'OrderId'.
src/types.ts:8:14
8 export type OrderId = string & { __brand: "order" };
~~~~~~~
'OrderId' is declared here.The related diagnostic shows you where OrderId is defined. That extra context is what makes branded types and other custom shapes understandable. The compiler is saying: "I know this looks like a string, but here's the declaration that says it's something more."
When an error mentions a type name you don't recognize, follow the related diagnostic to the source. Half the time the answer is right there in the type's definition — a comment, a stricter shape, or an exported helper that builds the value correctly.
node_modules file. What does that usually mean?Where to look first
When a build produces multiple errors, your instinct is to scan from the top. Resist that. The structure of tsc output is more strategic than chronological.
A repeatable triage:
- Count the errors. The summary line at the bottom is how many. If it's three or fewer, fix them in order. If it's twenty, something is wrong and the right move is to find the root cause first.
- Look for the "missing module" errors first.
TS2307: Cannot find module 'X'errors often cascade — one missing import generates dozens of downstream "type not found" errors. Fix the resolution, half the noise disappears. - Look for the most-cited file. If a single file appears in twelve errors, that's where the bug probably lives. The other eleven are downstream effects.
- Read the first error of any category. If you have eight
TS2345errors all referencing the same type, the first one is usually the most specific. The rest are repetitions at different call sites.
Found 23 errors in 4 files. Errors Files 14 src/types.ts 6 src/cart.ts 2 src/orders.ts 1 src/index.ts
The summary at the bottom of tsc output tells you exactly where to look. types.ts has fourteen errors — start there. The single error in index.ts is almost certainly downstream of the bigger problems and won't be fixable until upstream is clean.
Fixing one upstream error often deletes ten downstream ones. After every fix, re-run tsc --noEmit and watch the count drop. Don't fix the same error in twenty places when one fix at the source will do.
Three tactics for hard errors
When the message still doesn't help, three moves usually get you unstuck.
1. Hover the suspicious value. In your editor, hover the variable involved in the error. The popup shows its full inferred type. Often the inferred type is wider or narrower than you expected, and that mismatch is the bug.
2. Annotate explicitly to localize the failure. If const order = await getOrder(id) produces a type error two lines later, add an explicit annotation: const order: Order = await getOrder(id). Now the error happens at the assignment, not later. Localizing the error makes the cause obvious.
3. Search the error code. TS2345, TS2322, TS2307 — these are stable across versions. Searching the code along with a few key terms from the message often finds someone with the same pattern. The TypeScript docs and the issue tracker both reference these codes.
/* before — error happens at processOrder(), confusing */ const order = await getOrder(id); const tax = calcTax(order); processOrder(order); /* error here */ /* after — error happens at assignment, obvious */ const order: Order = await getOrder(id); /* error here */ const tax = calcTax(order); processOrder(order);
Adding the annotation moves the error closer to its source. The compiler now complains at the line where the wrong shape enters your code, not at the line where it's eventually used. That distance closes the gap between "where the symptom appears" and "where the bug is".
string | undefined. What's the next move?TS2307: Cannot find module 'lodash'. The package is installed. lodash ships no types of its own. What is the most likely fix?