The Error
Argument of type 'string' is not assignable to parameter of type 'number'. ts(2345)
You passed a value to a function, and TypeScript rejected it because the types don't line up. Nine times out of ten, you've got a string where the function wants a number โ but the same mismatch appears with boolean vs string, null vs object, and plenty of other combinations.
Good news: this is a compile-time error. TypeScript caught the bug before it reached production.
Why This Happens
TypeScript checks every function call against its declared parameter types. Pass a number param a string โ even "42" โ and the compiler refuses. It doesn't care that the value looks numeric.
The usual culprits:
- Reading from
process.argv,req.query, orURLSearchParamsโ these always return strings, even when the value is"42". - Grabbing a value from an HTML
<input>โinput.valueis alwaysstring, no exceptions. - Parsing JSON where a field arrived as
"10"instead of10. - A variable that started as
numberbut got widened tostring | numbersomewhere upstream. - Passing arguments to a library function in the wrong order.
Step-by-Step Fix
Step 1 โ Find the mismatch
TypeScript tells you the exact line. Open it, look at what you're passing, then check the function signature. The gap is usually obvious:
// ts(2345)
function double(n: number): number {
return n * 2;
}
const input = "5"; // type: string
console.log(double(input)); // โ string โ number mismatch
Step 2 โ Convert the value
A string that represents a number needs to be parsed before you pass it in.
const input = "5";
console.log(double(Number(input))); // โ
explicit, handles decimals
console.log(double(parseInt(input, 10))); // โ
better for integers โ always pass radix 10
console.log(double(+input)); // โ
concise, but easy to miss in code reviews
Use Number() when the value might be a decimal. Use parseInt(input, 10) when you specifically need an integer. Skip the unary + in team codebases โ it's a pain to grep and looks like a typo at a glance.
Step 3 โ Handle the common sources
CLI args via process.argv:
const raw = process.argv[2]; // string | undefined
const port = Number(raw);
if (isNaN(port)) throw new Error(`Invalid port: ${raw}`);
startServer(port); // โ
HTML input fields:
const input = document.getElementById("age") as HTMLInputElement;
const age = Number(input.value);
if (isNaN(age)) return; // catches empty string and "abc"
setAge(age); // โ
URL query params (Express / Fetch API):
// Express
app.get("/users", (req, res) => {
const page = Number(req.query.page); // req.query.page is string | string[] | ParsedQs
getUsers(page); // โ
});
// URLSearchParams
const params = new URLSearchParams(location.search);
const id = Number(params.get("id")); // get() returns string | null
if (isNaN(id)) throw new Error("Missing id param");
fetchItem(id); // โ
JSON payloads with stringly-typed numbers:
// Server returned { "count": "10" } instead of { "count": 10 }
const data = JSON.parse(response) as { count: string };
const count = Number(data.count);
renderList(count); // โ
Step 4 โ Narrow a union type
TypeScript sometimes widens a variable to string | number. A function that only accepts number will still reject it. Use a type guard to narrow before passing.
function applyDiscount(price: number): number {
return price * 0.9;
}
const rawPrice: string | number = getPrice();
// Option A: narrow with typeof
if (typeof rawPrice === "number") {
applyDiscount(rawPrice); // โ
TypeScript knows it's number here
}
// Option B: convert unconditionally
applyDiscount(Number(rawPrice)); // โ
works either way
Step 5 โ Fix the function signature instead
Sometimes the call site is fine and the signature is the problem. If a function is genuinely designed to handle both strings and numbers, update its parameter type to reflect that.
// Before โ too strict
function log(value: number): void {
console.log(value);
}
// After โ accepts both
function log(value: number | string): void {
console.log(value);
}
Only widen the type if the function actually handles both. Don't do it just to silence the error โ that defeats the whole point of TypeScript.
Verify the Fix
Run the compiler to confirm nothing remains:
# Full project check
npx tsc --noEmit
# Single file
npx tsc --noEmit src/yourfile.ts
# With ts-node
npx ts-node src/yourfile.ts
No output means zero type errors. In VS Code, the red squiggly disappears the moment you save the file.
Quick Reference
Number(x)โ converts string/boolean/null to number; returnsNaNif unparseableparseInt(x, 10)โ integer from string; silently drops trailing non-numeric chars ("42px"โ42)parseFloat(x)โ decimal from string; useful for"3.14"+xโ shorthand forNumber(x); avoid in shared codebases, it's hard to grep- Always validate with
isNaN()when the input comes from users or external sources
When Conversion Is the Wrong Fix
Seeing ts(2345) in a dozen places? Before scattering Number() calls across your codebase, check your data model. UUIDs and MongoDB ObjectIDs are strings by nature โ converting them to numbers at every call site is fighting the wrong battle. Type them as string from the start and the errors vanish for good. Widespread ts(2345) errors are usually a symptom of a type mismatch in your data layer, not a conversion problem at each individual boundary.

