TS2322: Type 'X' is not assignable to type 'Y'

Quick answer

A value's type doesn't match where you're assigning it. Read the error top-down to the innermost line to find the exact field, then make the types match — fix the value, correct the declared type, or narrow it. The most common variant is string | undefined not assignable to string under strictNullChecks — guard it or use ?? ''.

The exact error string

let count: number = "5";
// error TS2322: Type 'string' is not assignable to type 'number'.

interface User { id: number; name: string; }
const u: User = { id: 1, name: 42 };
// error TS2322: Type 'number' is not assignable to type 'string'.  (on name)

How to read a TS2322 error (find the real cause)

The first line names the two top-level types, but the actual mismatch is usually deeper. Read down to the innermost Types of property 'x' are incompatible / Type 'A' is not assignable to type 'B' line — that pinpoints the field and the exact types to change. In your editor, hover the red underline to see the same nested breakdown.

const users: User[] = [{ id: "42", name: "Ada" }];
// error TS2322: Type '{ id: string; name: string; }[]' is not assignable to type 'User[]'.
//   Type '{ id: string; name: string; }' is not assignable to type 'User'.
//     Types of property 'id' are incompatible.
//       Type 'string' is not assignable to type 'number'.   <-- the real cause

Ignore the noisy outer types and jump to the last line: id is a string where User.id is a number. That single field is what you fix.

Fix 1: make the value and the type agree

Decide which side is correct — the value or the declared type — and fix that one:

let count: number = 5;            // value corrected
let id: string = "5";            // or the type corrected to match the value

Fix 2: strictNullChecks — handle null/undefined

The single most common TS2322 is a possibly-undefined value assigned to a non-optional type. Narrow it before assigning:

function f(name?: string) {
  const s: string = name;          // ❌ string | undefined not assignable to string

  const s1: string = name ?? "";   // ✅ default
  if (name !== undefined) {
    const s2: string = name;       // ✅ narrowed
  }
}

Fix 3: widen the target with a union (when both are valid)

If the target legitimately holds more than one type, declare a union — but only when both values truly belong there:

let id: string | number;
id = 5;       // ✅
id = "abc";   // ✅

Fix 4: assertions — last resort

A type assertion silences the check without changing the value, so use it only when you genuinely know more than the compiler (e.g. right after validating external data):

const el = document.getElementById("app") as HTMLDivElement;   // you verified it's a div
const n = (value as unknown) as number;   // double assertion — a red flag, avoid

Worked example: TS2322 from a mistyped API payload

This is the most common real-world trigger on a JSON-heavy app: a hand-written interface that has drifted from what the API actually returns. The API sends an id as a quoted string, your interface says number, and the assignment fails:

interface User { id: number; name: string; }

// The API actually returns: { "id": "42", "name": "Ada" }
const res = await fetch("/api/user");
const user: User = await res.json();
// error TS2322: Type 'string' is not assignable to type 'number'. (on id)

The fix is accurate types, not a cast. Generate the interface from a real response with JSON to TypeScript — it types id as string to match the data — then convert deliberately if your app needs a number:

interface ApiUser { id: string; name: string; }    // generated from a real sample

const apiUser = (await res.json()) as ApiUser;
const user: User = { ...apiUser, id: Number(apiUser.id) };   // explicit conversion

TS2322 in React

React surfaces TS2322 in a few recurring spots:

// 1) useState infers from the initial value
const [user, setUser] = useState(null);          // inferred type: null
setUser({ id: 1, name: "Ada" });                 // ❌ TS2322
const [user2, setUser2] = useState<User | null>(null);   // ✅ type it

// 2) a typed prop given the wrong type
function Badge({ count }: { count: number }) { return <span>{count}</span>; }
<Badge count="3" />                              // ❌ Type 'string' is not assignable to type 'number'
<Badge count={3} />                              // ✅

// 3) controlled input value can't be null
<input value={name} />    // if name is string | null -> TS2322; use {name ?? ""}

Common variants of this message

VariantTypical causeFix
Type 'string' is not assignable to type 'number'wrong primitive typeconvert the value or fix the type
Type 'string | undefined' is not assignable to type 'string'strictNullChecksnarrow, or ?? ''
Type 'null' is not assignable to type 'string'null where non-null expectedallow | null or guard
Type 'X' is not assignable to type 'never'[] inferred as never[]annotate the variable
Object literal may only specify known propertiesexcess propertyremove/rename the extra key
'string' is not assignable to type '"asc" | "desc"'literal uniontype the source or as const

Debugging checklist

Frequently Asked Questions

What does "is not assignable to type 'never'" mean?

'never' usually means TypeScript inferred an empty type. The classic case is const arr = [] which is inferred as never[], so pushing or assigning a value fails. Annotate the variable — const arr: number[] = [] — so it isn't never. It also appears in the impossible branch of an exhaustive switch.

Why does the error say "Types of property 'x' are incompatible"?

That nested line is the real cause. The two outer types differ only because one inner property doesn't match. Read down to the innermost Type 'A' is not assignable to type 'B' — it names the exact field and the exact types to fix, instead of the broad outer types.

Does TS2322 fail the build or only show in the editor?

Both. tsc exits with a non-zero code on TS2322, so it fails CI and production builds, and the editor underlines it. They share the same type-checker, so fixing the type clears both. A red squiggle with a passing build usually means the editor and tsconfig disagree on settings.

How do I fix TS2322 on a React useState?

Give useState an explicit type argument. useState(null) infers the state type as null, so a later setUser({...}) is not assignable. Write useState<User | null>(null) so the state accepts the values you will actually set.

Should I suppress TS2322 with @ts-expect-error?

Prefer fixing the type. If you must suppress temporarily, @ts-expect-error is better than @ts-ignore because it itself errors if the line later stops having an error, so it self-cleans. Keep either rare, on the single offending line, and commented with why.

Why is 'unknown' not assignable to a specific type?

unknown is the type-safe top type: you must narrow it before assigning it to anything specific. Validate or guard the value (typeof, an in check, or a schema parse) and TypeScript narrows it, then the assignment is allowed. any would let it through silently — that is exactly the safety unknown adds.

Generate accurate types from your JSON

Paste a real API response and get a TypeScript interface whose field types match the data — nothing is uploaded to a server.

JSON to TypeScript JSON Formatter All Error References
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.