TS2345: Argument of type is not assignable to parameter of type - 10 Causes and Fixes

TS2345: Argument of type is not assignable to parameter of type is the TypeScript compiler telling you that a value you passed to a function does not satisfy the type that function expects. This is one of the most common TypeScript errors — and one of the most varied, because it fires everywhere a function call touches a mismatched type.

The error message itself always fills in two blanks: the type you actually gave, and the type the parameter demands. Reading those two types carefully is half the battle. The gap between them tells you exactly which property is missing, which union member is wrong, or which generic got inferred incorrectly.

This guide covers 10 concrete causes ranked by frequency, a diagnostic checklist for cases that do not match any single cause, and the type-theory reason this error exists at all. Software developer analyzing code on a tablet in a modern office workspace.

ErrorTS2345: Argument of type '{A}' is not assignable to parameter of type '{B}'.
Where it happensTypeScript compiler (tsc) — any version from 2.0 onward, in any editor or CI that runs type checking. Framework-agnostic.
What it meansYou called a function and passed a value whose type does not match what the function's parameter expects — TypeScript refuses to compile until the types align.

The Fast Fix

There is no single fix because the error covers every function-call type mismatch in the language. The fastest path is to read the two types printed in the error, find the exact difference, and fix whichever side is wrong.

If the argument is correct and the parameter type is too narrow, widen the parameter. If the parameter type is correct and your data is shaped wrong, reshape the data or add a type assertion as a last resort:

// If you are confident the value is correct at runtime:
someFunction(value as ExpectedType);

A type assertion silences the compiler without fixing the underlying mismatch — use it only when you have verified the runtime shape. For a structured diagnosis, walk the checklist below.

What Is Actually Causing It

1. Passing a string literal where a union of specific strings is expected

Reproduce it

function setDirection(dir: 'left' | 'right' | 'up' | 'down') {
  console.log(dir);
}

const d = 'left';
let direction = d; // type widens to `string`
setDirection(direction);
// TS2345: Argument of type 'string' is not assignable to parameter of type '"left" | "right" | "up" | "down"'

Why it happens — When you assign a string literal to a let variable, TypeScript widens it to string. The function parameter expects a narrow union of specific literal types, so string does not satisfy it.

The fix

Use const to keep the literal type, or add as const:

const direction = 'left'; // type is 'left', not string
setDirection(direction);

// Or with as const on an existing let:
let direction2 = 'left' as const;
setDirection(direction2);

Confirm it worked — Run tsc --noEmit. The error on the setDirection call disappears.


2. Object literal has extra or missing properties compared to the parameter type

Reproduce it

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

function greet(user: User) {
  console.log(user.name);
}

greet({ name: 'Alice', age: 30, role: 'admin' });
// TS2345: Argument of type '{ name: string; age: number; role: string; }' is not assignable to parameter of type 'User'.

Why it happens — TypeScript applies excess property checking to object literals passed directly to functions. The role property does not exist on User, so the compiler flags it. This catches typos and accidental data leaks.

The fix

Remove the extra property, or assign to an intermediate variable (which opts out of excess property checks):

// Option 1: remove the extra property
greet({ name: 'Alice', age: 30 });

// Option 2: extend the interface if role belongs there
interface AdminUser extends User {
  role: string;
}
function greetAdmin(user: AdminUser) { /* ... */ }

Confirm it worked — Run tsc --noEmit — the call compiles without error.


3. Passing null or undefined to a parameter that does not allow it

Reproduce it

function formatName(name: string): string {
  return name.toUpperCase();
}

const input: string | null = getUserInput();
formatName(input);
// TS2345: Argument of type 'string | null' is not assignable to parameter of type 'string'.

Why it happens — With strictNullChecks enabled (the default in most modern configs), null is not part of the string type. The compiler forces you to handle the null case before passing the value.

The fix

Narrow the type before the call:

const input: string | null = getUserInput();
if (input !== null) {
  formatName(input); // input is narrowed to string
}

Confirm it worked — Run tsc --noEmit with "strict": true in tsconfig.json. No error on the formatName call.


4. Array method callback returns the wrong type

Reproduce it

const ids: number[] = ['1', '2', '3'].map((s) => s);
// TS2345 fires at the assignment level, but the root is the map callback returning string instead of number

function sumIds(ids: number[]) { return ids.reduce((a, b) => a + b, 0); }
sumIds(['1', '2', '3'].map((s) => s));
// TS2345: Argument of type 'string[]' is not assignable to parameter of type 'number[]'.

Why it happens.map((s) => s) returns string[] because the source array is string[]. The function expects number[]. TypeScript traces the mismatch back to the argument.

The fix

Transform the values inside the callback:

sumIds(['1', '2', '3'].map((s) => Number(s)));
// Now the callback returns number, so map produces number[]

Confirm it worked — Hover over the .map() call in your editor — the inferred return type should show number[].


5. Generic type parameter inferred differently than expected

Reproduce it

function merge<T>(a: T, b: T): T {
  return { ...a, ...b };
}

merge({ name: 'Alice' }, { name: 'Bob', age: 30 });
// TS2345: Argument of type '{ name: string; age: number; }' is not assignable to parameter of type '{ name: string; }'.

Why it happens — TypeScript infers T from the first argument as { name: string }. The second argument has an extra age property that does not exist on the inferred T, triggering the error.

The fix

Explicitly provide the generic type parameter that covers both shapes:

merge<{ name: string; age?: number }>({ name: 'Alice' }, { name: 'Bob', age: 30 });

// Or redesign the function to accept two different types:
function merge2<A, B>(a: A, b: B): A & B {
  return { ...a, ...b };
}

Confirm it worked — Run tsc --noEmit. The merge call compiles, and hovering over the result shows the expected shape.


6. Passing a union type where only one member is accepted

Reproduce it

function processId(id: number) {
  return id * 2;
}

const value: string | number = parseInput();
processId(value);
// TS2345: Argument of type 'string | number' is not assignable to parameter of type 'number'.

Why it happens — A union type (string | number) includes members that do not satisfy number. TypeScript requires you to narrow the union before passing it to a function that only accepts one member.

The fix

Narrow with a type guard:

const value: string | number = parseInput();
if (typeof value === 'number') {
  processId(value); // narrowed to number
}

Confirm it worked — Run tsc --noEmit — no error. Add an else branch to handle the string case if needed.


More causes (4 remaining)

7. Enum value passed where a different enum or its underlying type is expected

Reproduce it

enum Status { Active, Inactive }
enum Role { Admin, User }

function checkStatus(s: Status) {
  return s === Status.Active;
}

checkStatus(Role.Admin);
// TS2345: Argument of type 'Role.Admin' is not assignable to parameter of type 'Status'.

Why it happens — TypeScript numeric enums are nominally typed — even though Role.Admin and Status.Active are both 0 at runtime, they are distinct types. The compiler treats them as incompatible.

The fix

Pass the correct enum member:

checkStatus(Status.Active);

If you genuinely need to convert, map explicitly rather than casting:

const statusFromRole = new Map<Role, Status>([
  [Role.Admin, Status.Active],
  [Role.User, Status.Inactive],
]);
checkStatus(statusFromRole.get(Role.Admin)!);

Confirm it worked — Run tsc --noEmit. The call compiles, and the enum relationship is explicit in the mapping.


8. Promise or async value passed where the resolved type is expected

Reproduce it

async function fetchName(): Promise<string> {
  return 'Alice';
}

function greetUser(name: string) {
  console.log(`Hello, ${name}`);
}

greetUser(fetchName());
// TS2345: Argument of type 'Promise<string>' is not assignable to parameter of type 'string'.

Why it happens — An async function returns Promise<string>, not string. You must await the promise to extract the resolved value before passing it to a synchronous parameter.

The fix

Await the call inside an async context:

async function main() {
  const name = await fetchName();
  greetUser(name); // name is string, not Promise<string>
}

Confirm it worked — Run tsc --noEmit. The error disappears. Check that the calling function is marked async.


9. Readonly array passed to a mutable array parameter

Reproduce it

function shuffle(arr: number[]) {
  for (let i = arr.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr;
}

const data = [1, 2, 3] as const;
shuffle(data);
// TS2345: Argument of type 'readonly [1, 2, 3]' is not assignable to parameter of type 'number[]'.

Why it happensas const makes the array readonly. A number[] parameter implies the function may mutate the array, so TypeScript blocks passing a readonly value to a mutable parameter.

The fix

If the function does not need to mutate, type the parameter as readonly number[]. If it does mutate, copy the array first:

// Option 1: make the parameter readonly (if the function can work without mutation)
function shuffle(arr: readonly number[]): number[] {
  const copy = [...arr];
  // shuffle copy instead
  return copy;
}

// Option 2: copy at the call site
shuffle([...data]);

Confirm it worked — Run tsc --noEmit. The call compiles. If you used option 1, confirm the function no longer mutates its argument.


10. Third-party library type version mismatch with your code's types

Reproduce it

// Your code uses @types/react 18.x
import { MouseEvent } from 'react';

// A library exports a handler typed against @types/react 17.x
import { onItemClick } from 'some-ui-lib';

onItemClick((e: MouseEvent<HTMLButtonElement>) => {
  console.log(e.currentTarget);
});
// TS2345: Argument of type '(e: MouseEvent<HTMLButtonElement>) => void'
//   is not assignable to parameter of type '(e: MouseEvent<HTMLButtonElement>) => void'.

Why it happens — The two MouseEvent types come from different versions of @types/react in your node_modules. Even though the names and shapes look identical, TypeScript resolves them to separate declarations, making them incompatible.

The fix

Align the type versions. Check for duplicates and deduplicate:

npm ls @types/react
# Look for multiple versions in the tree

# In package.json, add a resolutions/overrides field:
# npm (package.json):
"overrides": {
  "@types/react": "^18.2.0"
}

# yarn (package.json):
"resolutions": {
  "@types/react": "^18.2.0"
}

npm install   # or yarn install

Confirm it worked — Run npm ls @types/react — only one version should appear. Then run tsc --noEmit to confirm the error is gone.

High-resolution image of colorful programming code highlighted on a computer screen. ## None of Those? Narrow It Down
  1. Read the full error message. TS2345 always prints two types — the type you gave and the type expected. Copy both into a text editor and diff them character by character. The difference is either a missing property, an extra property, a union mismatch, or a readonly/Promise wrapper.
  2. Hover over the argument in your editor. Check the inferred type. If it says string when you expected 'left' | 'right', the problem is type widening — look at how the variable was declared (let vs const, missing as const).
  3. Check for null or undefined in the inferred type. If the argument's type is T | null or T | undefined and the parameter is T, you need a null guard. Search for strictNullChecks in your tsconfig to confirm it is enabled.
  4. Inspect generic inference. If the function is generic, hover over the call to see what T was inferred as. If the inference is wrong, supply the type parameter explicitly: fn<ExpectedType>(arg).
  5. Check for duplicate type declarations. Run npm ls <package-name> for the type package involved. Two versions of the same @types/* package produce structurally identical but nominally distinct types.
  6. Verify async/await. If the error says Promise<X> is not assignable to X, you forgot to await. Check that the calling function is async.
  7. Look at readonly vs mutable. If the error mentions readonly, check whether the argument was created with as const, Readonly<>, or ReadonlyArray<>. Either make the parameter readonly or copy the value before passing it.
  8. Check the tsconfig strict family. Temporarily set "strict": false and recompile. If the error disappears, the mismatch is caused by a strict-mode check (usually strictNullChecks or strictFunctionTypes). This tells you which strict flag is surfacing the issue — do not ship with strict off.

Why This Error Exists At All

TypeScript's type system is structural: two types are compatible if their shapes match, regardless of their names. TS2345 fires when the compiler walks the structure of your argument and the structure of the parameter and finds a point where they diverge — a property that is missing, a property whose type is different, or a wrapper type (Promise, readonly, a different union member) that changes the shape.

This is a deliberate design decision. JavaScript is dynamically typed; functions will happily accept any value at runtime and crash later when a property is missing or a method does not exist. TypeScript moves that crash to compile time by requiring that every function call's argument structurally satisfies the parameter type. The cost is the TS2345 error. The payoff is that if your code compiles, the function will never receive a shape it was not written to handle.

The reason TS2345 feels so varied — string-literal widening, null mismatches, generic inference failures, readonly conflicts — is that all of these are instances of the same structural check failing. Once you internalise that the compiler is always doing the same thing ("does shape A fit into slot B?"), you can read any TS2345 message by diffing the two shapes printed in the error and fixing whichever side is wrong. Female engineer using laptop to analyze vehicle data inside a car for testing purposes.

Stop It From Coming Back

  • Enable strict: true in tsconfig.json from day one. This turns on strictNullChecks, strictFunctionTypes, and other flags that surface TS2345 errors early, before they compound into harder-to-trace mismatches deeper in the call stack.
  • Use as const on literal configuration objects and arrays. This preserves narrow literal types and prevents widening, which eliminates the most common source of "string is not assignable to union" errors.
  • Add an ESLint rule for @typescript-eslint/no-unsafe-argument. This flags cases where any-typed values slip into typed function parameters — a common way TS2345 errors get deferred until a refactor tightens the types.
  • Pin @types/* packages with overrides or resolutions. Duplicate type declaration packages are a frequent cause of structurally-identical-but-incompatible types. A single version constraint prevents this.
  • Prefer readonly parameter types for functions that do not mutate their input. This prevents callers from needing to copy arrays and objects just to satisfy mutability requirements, and documents intent.
  • Write narrowing utility functions for discriminated unions. A single isStatus(value): value is Status guard, tested once, eliminates repeated inline type checks at every call site.

Related Guides

Errors You Will Probably Hit Next

  • TS2322: Type '{A}' is not assignable to type '{B}' — the assignment-side counterpart of TS2345, fires on variable assignments and return statements instead of function arguments.
  • TS2352: Conversion of type '{A}' to type '{B}' may be a mistake — fires when a type assertion (as) cannot bridge the gap between two types, often the next error you see after attempting to fix TS2345 with a cast.
  • TS2554: Expected {N} arguments, but got {M} — fires when the argument count is wrong rather than the argument type; often confused with TS2345 when an overload does not match.

Every TS2345 error is the same question: does the shape you passed fit the shape the function expects? Read the two types in the error message, diff them, and fix whichever side is wrong. If the argument type is correct, widen the parameter. If the parameter type is correct, narrow or transform the argument. Resist the urge to cast with as until you have confirmed the runtime shape actually matches — a cast just moves the crash from compile time back to runtime, which is the problem TypeScript was built to prevent.

댓글

이 블로그의 인기 게시물

TypeError: Cannot read properties of undefined (reading 'map') - 11 Causes and Fixes

Error: ERR_MODULE_NOT_FOUND: Cannot find package - 10 Causes and Fixes

npm ERR! code ERESOLVE unable to resolve dependency tree: 9 Causes and Fixes