Quick Answer

Type 'X' is not assignable to type 'Y' means you supplied a value of type X where a Y is required, and an X is not always a valid Y. Read it as "I have an X; the slot wants a Y." When the message indents further with Types of property '...' are incompatible, follow the indentation - the deepest line is the real mismatch. The most common trigger is undefined slipping into a union from an array lookup, a Map, or an optional property.

How to Read the Error

The shape is always Type 'A' is not assignable to type 'B'. B is what the position requires - a variable's declared type, a parameter, a return type, an object property. A is what you gave it. Assignability runs one direction: every number is a valid number | undefined, but not every number | undefined is a valid number.

Two error codes carry this. TS2322 is for assignments and object properties. TS2345 is for function arguments and reads Argument of type 'A' is not assignable to parameter of type 'B'. When the types nest, TypeScript prints a stack:

Type '(id: string) => void' is not assignable to type 'Handler'.
  Types of parameters 'id' and 'id' are incompatible.
    Type 'number' is not assignable to type 'string'.

That is real verified output. Read it top to bottom: the headline names the outer types, each indent narrows to the specific incompatible piece, and the last line is what to fix - here a handler declared with string where Handler supplies a number.

The undefined That Sneaks In

Under strict (specifically strictNullChecks), many standard-library calls return T | undefined:

const names: string[] = ["asha", "ravi"];
const found = names.find(n => n === "asha");
greet(found);

Verified error:

Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

Array.prototype.find, Map.get, indexed access with noUncheckedIndexedAccess, document.querySelector, and optional properties all do this. TypeScript is correct - the item might be missing. Fixes, best first:

  • Narrow it: if (found !== undefined) { greet(found); } - inside the block found is string. Verified to compile clean.
  • Default it: scores.get("ravi") ?? 0 produces number. Verified clean.
  • Assert it exists: if (!found) throw new Error("missing name");

Avoid found!, the non-null assertion, unless you genuinely know more than the compiler - it hides the check without making the value safe, so if the value really is missing you get a runtime crash instead of a compile error. If a value is legitimately optional, consider making that explicit in the type (name?: string) so every caller is forced to handle the absent case.

Literal Widening and Union Typos

type Status = "pending" | "active" | "closed";
let raw = "active";
const s: Status = raw; // Type 'string' is not assignable to type 'Status'.

Verified. let raw = "active" is inferred as string, not "active" - TypeScript widens the literal because a let can be reassigned to any string. Switch to const raw = "active" and the type stays "active", which is assignable to Status. For object properties use as const: const cfg = { mode: "dark" } as const makes cfg.mode the type "dark" instead of string. Verified: both compile clean.

The sibling error is a plain typo:

setStatus("actve");
// Argument of type '"actve"' is not assignable to parameter of type 'Status'.

This is the union type doing its job - the misspelling is caught at compile time instead of slipping through to a runtime bug where status is silently never "active" and a status check never fires. The same widening happens with numbers and booleans in a let, but string-literal unions are where it bites most often, because that is where the exact spelling carries meaning.

Objects: Wrong, Extra, and Nested

Three distinct object errors show up:

  • Wrong property type - TS2322: Type 'number' is not assignable to type 'string' on { id: 1, name: 42 } where name is string.
  • Excess property - TS2353: Object literal may only specify known properties, and 'verbose' does not exist in type 'Options'.
  • Nested mismatch - the error drills down through data to Type 'string' is not assignable to type 'number' on count. Follow the indentation to the leaf.

The excess-property one has a twist. connect({ timeout: 1000, retries: 3, verbose: true }) is rejected, but assigning that identical object to a variable first and passing the variable is accepted - verified. Structural typing allows extra properties in general; the literal check is a narrow safety net aimed at typos in options objects passed inline.

The Gotcha: as Does Not Convert Anything

const data = JSON.parse('{"id": 1}') as User;
console.log(data.name.toUpperCase());

tsc compiles this with zero errors - verified, exit code 0. At runtime it throws TypeError: Cannot read properties of undefined (reading 'toUpperCase') - also verified.

A type assertion (as User, or the older <User>value) tells the compiler "treat this as User" and switches off checking for that value. It does not parse, validate, or coerce anything. When the data does not match the type, the error just relocates from compile time, where it is cheap, to runtime, where your users hit it.

Legitimate uses are narrow: asserting a more specific DOM type after you have checked (e.target as HTMLInputElement), or as const. For anything from outside your code - API responses, JSON.parse, localStorage, form fields - validate instead, with a type-guard function or a schema library such as Zod that checks the shape and returns a typed value. Writing as just to make an error disappear is usually hiding a real bug.

A Quick Checklist

When you hit "not assignable":

  • Work out which type is the slot and which is your value - the message reads Type <yours> is not assignable to type <required>.
  • If undefined or null is in your type, narrow with an if, default with ??, or restructure so the value cannot be absent.
  • If your value is string but the slot wants a union of string literals, change let to const or add as const.
  • If it is a function, check the parameter and return types - the indented lines name the exact one.
  • If it is an object literal with an "unknown property" message, you have a misspelled key or a field that belongs somewhere else.
  • Reach for as last, and only when you can say out loud why you know more than the compiler.

Frequently Asked Questions

What does "is not assignable to type" mean in one sentence? You put a value somewhere its type is not accepted - the value's type is broader than, or different from, what that position requires.
Why is string | undefined not assignable to string? Because the value could be undefined at runtime, and calling string methods on undefined throws. Narrow it with a check, or supply a default with the ?? operator.
How do I fix "Type 'string' is not assignable" to a union of string literals? The value was widened to string. Use const instead of let, or add "as const", so TypeScript keeps the narrow literal type.
What is error TS2353 about "known properties"? An object literal has a property the target type does not declare - almost always a misspelled key or a field that belongs elsewhere. It only checks object literals passed inline.
Is using "as" to silence the error safe? Only when you genuinely know the runtime type and the compiler cannot. "as" does no conversion or validation, so if you are wrong the code compiles and then crashes at runtime.