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.
| Error | TS2345: Argument of type '{A}' is not assignable to parameter of type '{B}'. |
|---|---|
| Where it happens | TypeScript compiler (tsc) — any version from 2.0 onward, in any editor or CI that runs type checking. Framework-agnostic. |
| What it means | You 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
Jump to your case
- 1. Passing a string literal where a union of specific strings is expected
- 2. Object literal has extra or missing properties compared to the parameter type
- 3. Passing null or undefined to a parameter that does not allow it
- 4. Array method callback returns the wrong type
- 5. Generic type parameter inferred differently than expected
- 6. Passing a union type where only one member is accepted
- 7. Enum value passed where a different enum or its underlying type is expected
- 8. Promise or async value passed where the resolved type is expected
- 9. Readonly array passed to a mutable array parameter
- 10. Third-party library type version mismatch with your code's types
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 happens — as 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.
- 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/Promisewrapper. - Hover over the argument in your editor. Check the inferred type. If it says
stringwhen you expected'left' | 'right', the problem is type widening — look at how the variable was declared (letvsconst, missingas const). - Check for
nullorundefinedin the inferred type. If the argument's type isT | nullorT | undefinedand the parameter isT, you need a null guard. Search forstrictNullChecksin your tsconfig to confirm it is enabled. - Inspect generic inference. If the function is generic, hover over the call to see what
Twas inferred as. If the inference is wrong, supply the type parameter explicitly:fn<ExpectedType>(arg). - 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. - Verify async/await. If the error says
Promise<X>is not assignable toX, you forgot toawait. Check that the calling function isasync. - Look at
readonlyvs mutable. If the error mentionsreadonly, check whether the argument was created withas const,Readonly<>, orReadonlyArray<>. Either make the parameter readonly or copy the value before passing it. - Check the tsconfig
strictfamily. Temporarily set"strict": falseand recompile. If the error disappears, the mismatch is caused by a strict-mode check (usuallystrictNullChecksorstrictFunctionTypes). 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.
Stop It From Coming Back
- Enable
strict: truein tsconfig.json from day one. This turns onstrictNullChecks,strictFunctionTypes, and other flags that surface TS2345 errors early, before they compound into harder-to-trace mismatches deeper in the call stack. - Use
as conston 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 whereany-typed values slip into typed function parameters — a common way TS2345 errors get deferred until a refactor tightens the types. - Pin
@types/*packages withoverridesorresolutions. Duplicate type declaration packages are a frequent cause of structurally-identical-but-incompatible types. A single version constraint prevents this. - Prefer
readonlyparameter 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 Statusguard, 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.
댓글
댓글 쓰기