Read errors like a local
Most of a TypeScript workday is a loop: write, compile, read an error, fix. Decoding the error text quickly is therefore a core skill in its own right, and it is the difference between a ten-second fix and an hour of guessing.
TypeScript errors follow patterns. Learn six and you can decode almost everything:
| Error | Real meaning |
|---|---|
Type 'X' is not assignable to type 'Y' | value of the wrong type, check the annotation |
Property 'p' does not exist on type 'T' | typo, or you must narrow the union first |
Argument of type 'X' is not assignable to parameter... | wrong argument at a call site |
Expected 2 arguments, but got 1 | missing argument, check optional ? |
Parameter 'x' implicitly has an 'any' type | annotate the parameter, strict mode |
'x' is possibly 'null' or 'undefined' | add a null check before use |
The habit that matters: read the error bottom to top and inside out. The last line usually names the exact property that mismatched, and the reported file and line point at the true location.
Decoding a missing-method error
The message Property 'toUpperCase' does not exist on type 'string | number' means you must narrow the union before using a string-only method, for example with typeof.
This is the union rule from lesson 4-1: only operations valid for every member are allowed. The value might be a number at runtime, and numbers have no toUpperCase, so the compiler refuses the whole expression.
The error names the union rather than a single type, and that is the clue pointing you at the fix. Wrap the use in if (typeof value === "string") and the method becomes available inside that branch.
The flag behind an implicit-any message
The option is noImplicitAny, and its name is literally in the error's wording.
Under this flag, which strict: true enables as part of the family from lesson 8-1, a parameter with no annotation and no inferable type is an error instead of silently becoming any.
The fix is to annotate the parameter, as in function f(user: User). It is worth doing rather than suppressing, because an unannotated parameter is the single widest hole through which unchecked values enter a typed codebase.
birthday: two errors in three lines
This program has two type errors: a misspelled property read and a string where a number belongs.
The starting point:
interface User { name: string; age: number; } function birthday(user: User): User { return { name: user.nmae, age: user.age + "1" }; } const ada: User = { name: "Ada", age: 36 }; const older = birthday(ada); console.log(older.name + " is now " + older.age);
The corrected version:
interface User { name: string; age: number; } function birthday(user: User): User { return { name: user.name, age: user.age + 1 }; } const ada: User = { name: "Ada", age: 36 }; const older = birthday(ada); console.log(older.name + " is now " + older.age);
Output
Ada is now 37Decoding each error
- The first error even suggests the fix:
Did you mean 'name'?. In plain JavaScriptuser.nmaewould have producedundefinedand printed"undefined is now 37". user.age + "1"builds the string"361", because a+with a string on one side concatenates. Changing the quoted"1"to the number1restores arithmetic.- Both errors are variants of
not assignable, and the table earlier in this lesson decodes them. Notice that the second one is caught at thereturn, since the object no longer matches the declaredUserreturn type.
findEmail: fixing a possibly-null error
findEmail can return null, so the compiler reports 'email' is possibly 'null' until the caller narrows. Here is the corrected version.
function findEmail(users: { name: string; email: string }[], target: string): string | null { for (const u of users) { if (u.name === target) { return u.email; } } return null; } const users = [{ name: "ada", email: "ada@example.com" }]; const email = findEmail(users, "ada"); if (email !== null) { console.log(email.toUpperCase()); } else { console.log("no email"); }
Output
ADA@EXAMPLE.COM
Reading the fix
- The table row for
possibly nullprescribes exactly this: add a null check before use. - The final log is wrapped in
if (email !== null) { ... } else { console.log("no email"); }, so both outcomes of the search are handled. email?.toUpperCase() ?? "no email"would also satisfy the compiler and fits on one line. Both forms are idiomatic, and theifversion is clearer when the two branches do substantially different work.
Where to go next
You now hold the working core of TypeScript: annotations and inference (Unit 1), the core types (Unit 2), interfaces (Unit 3), unions and narrowing (Unit 4), typed functions (Unit 5), generics (Unit 6), utility types and boundary checking (Unit 7), and project configuration (Unit 8).
Everything else in the language, mapped types, conditional types, decorators, is built from these pieces, and you can learn each one when a real codebase puts it in front of you. The best next step is to use TypeScript by default: your practice projects, your DSA solutions in the editor, and any React or Node work ahead. Strict mode on, any off, and let the compiler carry the details.
The one false claim about TypeScript
The false statement is that TypeScript types make the compiled program run faster at runtime.
Types never reach runtime, as lesson 1-1 established, so they cannot speed the program up. tsc erases every annotation and emits plain JavaScript, which is byte-for-byte what an untyped version would have been. Their value lies elsewhere: catching bugs at compile time, powering editor tooling, and documenting intent for the next reader.
Everything else you might have been tempted by is true, and each is something you have already used. Generics keep types flowing through reusable code, narrowing proves a union is safe to touch, and tsc erases types on the way to JavaScript.