Fix "Argument of type X is not assignable to parameter of type Y" (TS2345)

TL;DR: Error TS2345 means you passed an argument whose type is wider than, or shaped differently from, the parameter the function expects. TypeScript blocks the call because the value could break the function at runtime. The fix is to narrow, guard, or correct the argument so its type matches the parameter, not to silence the compiler with as.

What TS2345 actually means

The full message reads Argument of type 'X' is not assignable to parameter of type 'Y'. It always fires at a call site. X is the type of the value you passed, and Y is the type the function's parameter declares. The compiler runs an assignability check: is every possible value of X also a valid Y? If the answer is no, it stops you. TS2345 is the call-site cousin of TS2322, which is the same mismatch on a plain assignment.

Read it in plain English as: "the value you are handing in might not satisfy what this function requires." Below are the six causes you will hit most often, each with the failing code, the error TypeScript prints, and the fix that resolves it properly.

Cause 1: string | undefined passed where string is required

The classic. An optional value, an environment variable, or a lookup that might miss produces T | undefined, but the function wants a plain T.

function greet(name: string) {
  return `Hello, ${name.toUpperCase()}`;
}

const input: string | undefined = process.env.USER;

greet(input);
// Argument of type 'string | undefined' is not assignable to
// parameter of type 'string'.

Fix it by narrowing before the call, or by supplying a fallback so the value can never be undefined:

if (input !== undefined) {
  greet(input); // narrowed to string
}

// or collapse undefined with a default
greet(input ?? "friend"); // nullish coalescing yields a string

Cause 2: A wide string passed where a literal union is required

The parameter accepts only specific string literals, but your value is typed as the general string, which is wider.

type Direction = "up" | "down";

function move(dir: Direction) {}

const raw = "up"; // inferred as string, not "up"

move(raw);
// Argument of type 'string' is not assignable to
// parameter of type 'Direction'.

string is a superset of "up" | "down", so the compiler rejects it. Fix it by narrowing the type at its source:

const raw = "up" as const; // type is the literal "up"
move(raw); // ok

// or annotate the variable directly
const dir: Direction = "up";
move(dir);

If the string genuinely arrives from outside (JSON, form input), validate it at runtime and narrow with a type guard before the call.

Cause 3: Object shape mismatch (missing or excess properties)

The argument object is missing a property the parameter requires, or it carries a property the parameter type does not allow.

interface Options {
  retries: number;
  timeout: number;
}

function run(opts: Options) {}

run({ retries: 3 });
// Argument of type '{ retries: number; }' is not assignable to
// parameter of type 'Options'.
//   Property 'timeout' is missing in type '{ retries: number; }'.

Fix it by supplying the missing property, or by making it optional in the type if it truly is optional:

run({ retries: 3, timeout: 5000 }); // complete object

// or, if timeout has a sensible default, mark it optional:
interface Options {
  retries: number;
  timeout?: number;
}

The reverse also triggers TS2345: an object literal with an extra key gets excess-property-checked. Remove the stray key, or widen the parameter type to include it.

Cause 4: null passed where the parameter is optional but not nullable

undefined and null are distinct types in TypeScript. An optional parameter (x?: T) accepts T | undefined, but it does not accept null.

function setLabel(text?: string) {}

const value: string | null = getLabel();

setLabel(value);
// Argument of type 'string | null' is not assignable to
// parameter of type 'string | undefined'.
//   Type 'null' is not assignable to type 'string | undefined'.

Fix it by converting null to undefined, or by widening the parameter to accept both:

setLabel(value ?? undefined); // null becomes undefined

// or accept both explicitly
function setLabel(text?: string | null) {}

Cause 5: A union that has not been narrowed

You hold a union type, the function wants one specific arm of it, and you have not yet proven which arm you are holding.

function area(shape: { kind: "circle"; r: number }) {
  return Math.PI * shape.r ** 2;
}

type Shape =
  | { kind: "circle"; r: number }
  | { kind: "square"; side: number };

const s: Shape = getShape();

area(s);
// Argument of type 'Shape' is not assignable to parameter of type
// '{ kind: "circle"; r: number; }'.

Fix it by narrowing the discriminated union with a check on its discriminant before you call:

if (s.kind === "circle") {
  area(s); // narrowed to the circle arm
}

Discriminant checks like this are the idiomatic fix. The type narrowing lesson covers typeof, in, instanceof, and user-defined guards in depth.

Cause 6: unknown passed where a concrete type is required

unknown is the top type: it can hold anything, so it is assignable to nothing but unknown and any until you narrow it. Values from JSON.parse, catch clauses, and untyped third-party APIs are the usual sources.

function total(amount: number) {
  return amount * 1.2;
}

const data: unknown = JSON.parse('{"amount": 10}');

total(data);
// Argument of type 'unknown' is not assignable to
// parameter of type 'number'.

Fix it by validating and narrowing, not by casting blindly:

if (typeof data === "number") {
  total(data); // narrowed to number
}

// for structured data, check the shape with a type guard
function isPayload(v: unknown): v is { amount: number } {
  return (
    typeof v === "object" &&
    v !== null &&
    "amount" in v &&
    typeof (v as Record<string, unknown>).amount === "number"
  );
}

if (isPayload(data)) {
  total(data.amount);
}

For anything beyond trivial shapes, a schema validator such as Zod turns unknown input into a fully typed value in one step; see the validation lesson.

The escape hatch, and why to avoid it

You can silence TS2345 with a type assertion: greet(input as string). This tells the compiler to trust you and removes the check entirely. If your assumption is wrong, you get the exact runtime crash the error was trying to prevent, usually Cannot read properties of undefined. Reserve as for the rare case where you genuinely know more than the compiler and cannot express it any other way. In every cause above, narrowing or correcting the type is safer and just as short.

Quick diagnosis checklist

Run through these in order when TS2345 appears:

  • Does X include undefined or null that Y does not? Narrow with a check, or coalesce with ??.
  • Is X a wide string or number where Y wants a literal? Use as const or annotate the variable.
  • Is X an object missing a required property? Add it, or make the property optional.
  • Is X a union? Narrow it with a typeof, in, or discriminant check first.
  • Is X unknown or loosely typed? Validate it at runtime with a type guard.

Reading the exact wording of X and Y in the message almost always points straight at which of these you are looking at. The debugging TypeScript lesson has more on decoding compiler errors quickly.

If the mismatch involves derived types like Partial<T> or Pick<T, K>, our utility types guide explains what those types actually resolve to, which often reveals why an argument does not fit.

Next steps