Why These Five Errors Cover Most of TypeScript
TypeScript's error messages are precise but terse, and the same handful of error codes account for the overwhelming majority of "wait, why won't this compile" moments. These five — TS2322, TS2339, TS2345, TS2532, and TS2304 — are all variations on one underlying question the compiler is asking: does the value you're using actually match the shape you told me it would have?
For each one: the exact error, the root cause, how to diagnose it, the fix, and when reaching for a type assertion is legitimate versus when it's papering over a real bug.
1. Type 'X' is not assignable to type 'Y' (TS2322)
The exact error:
Type 'string' is not assignable to type 'number'.
Root cause: You're assigning a value to a variable, property, or return position whose declared type doesn't accept it. This is TypeScript's core job — catching the moment a value's actual type diverges from its expected type.
interface User {
age: number;
}
const user: User = {
age: "30", // TS2322: Type 'string' is not assignable to type 'number'
};
Diagnosis: Hover the flagged value in your editor — TypeScript shows both the actual and expected type. Trace where the value originated: is it coming from an API response (often typed as any or unknown until parsed), a form input (string by default), or a miscast literal?
Fix:
const user: User = {
age: 30, // number literal, not a string
};
// Or, if the source is genuinely a string you control (e.g. form input):
const user: User = {
age: Number(ageInput),
};
When it's a real bug vs. an assertion: If the value is coming from a form, URL param, or JSON.parse, it's genuinely a string at runtime and needs an actual conversion (Number(), parseInt) — not an assertion. An assertion (age as number) would compile but crash or silently misbehave downstream, because the string "30" doesn't become a number just because you told the compiler it is.
2. Property 'X' does not exist on type 'Y' (TS2339)
The exact error:
Property 'firstName' does not exist on type 'User'.
Root cause: You're accessing a property the compiler doesn't believe exists on that type — either it's a typo, the interface is out of date, or you're accessing a property that only exists on one branch of a union type.
interface User {
name: string;
}
function greet(user: User) {
console.log(user.firstName); // TS2339: no such property on User
}
Diagnosis: Check for a typo first (name vs. firstName) — this is the single most common cause. If there's no typo, check whether the interface was hand-written and has drifted out of sync with the actual API response shape.
Fix:
interface User {
name: string;
firstName?: string; // add the field if it's genuinely part of the shape
}
If the type comes from an external API, regenerate it from a real response rather than patching by hand — this is exactly what the JSON to TypeScript tool is for: paste a real API response, get an accurate interface, and avoid this class of error at the source.
When it's a union type issue:
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
function area(s: Shape) {
return s.radius * s.radius; // TS2339: radius doesn't exist on the 'square' branch
}
Here the fix is narrowing, not an assertion:
function area(s: Shape) {
if (s.kind === "circle") return s.radius * s.radius;
return s.side * s.side;
}
3. Argument of type 'X' is not assignable to parameter of type 'Y' (TS2345)
The exact error:
Argument of type 'string' is not assignable to parameter of type 'number'.
Root cause: Same underlying check as TS2322, but triggered at a function call instead of a variable assignment — the value you're passing doesn't match the parameter's declared type.
function double(n: number): number {
return n * 2;
}
double("5"); // TS2345
Diagnosis: Check the function's signature and trace where the argument value comes from. This is extremely common when passing event.target.value (always a string) into a function expecting a number.
Fix:
double(Number("5"));
// Or widen the function to accept both, if that's genuinely valid:
function double(n: number | string): number {
return Number(n) * 2;
}
When it's a real bug vs. an assertion: If the caller genuinely has a string that represents a number, convert it — don't assert. An assertion here ("5" as unknown as number) would compile but "5" * 2 behaves correctly in JS due to coercion, while more complex string values would silently produce NaN instead of a compile error. Converting explicitly keeps the failure visible.