This TypeScript error occurs when attempting to assign a string value to a variable, property, or parameter that expects a number type. TypeScript's static type checking prevents this at compile time to avoid runtime type errors.
This error is TypeScript's way of enforcing type safety during compile time. When you declare a variable, parameter, or property with a specific type (in this case, `number`), TypeScript ensures that only values of that type can be assigned to it. The error "Type 'string' is not assignable to type 'number'" indicates that you're trying to assign a string value where TypeScript expects a number. This commonly occurs when reading data from HTML inputs, API responses, URL parameters, or environment variables—sources that typically return string values even when they contain numeric characters. TypeScript prevents this assignment because strings and numbers are fundamentally different types with different operations and behaviors. For example, the string "5" + "3" equals "53" (concatenation), while the number 5 + 3 equals 8 (addition). By catching these mismatches at compile time, TypeScript helps prevent subtle runtime bugs that would be difficult to debug.
The Number() constructor is the most versatile method for converting strings to numbers. It handles both integers and floating-point numbers:
// Before: Type error
let age: number = "25"; // Error: Type 'string' is not assignable to type 'number'
// After: Correct conversion
let age: number = Number("25"); // age = 25
// Works with decimals
let price: number = Number("19.99"); // price = 19.99
// Returns NaN for invalid inputs
let invalid: number = Number("abc"); // invalid = NaNAlways check for NaN after conversion if the input might be invalid:
const input = "123";
const value = Number(input);
if (isNaN(value)) {
console.error("Invalid number input");
} else {
console.log("Valid number:", value);
}When you specifically need an integer (whole number), use parseInt(). This function parses a string and returns an integer, optionally specifying the radix (base):
// Parse decimal integers
let count: number = parseInt("42", 10); // count = 42
// Automatically removes decimal portion
let rounded: number = parseInt("42.7", 10); // rounded = 42
// Handles leading whitespace
let spaced: number = parseInt(" 100", 10); // spaced = 100
// Can extract numbers from mixed strings
let mixed: number = parseInt("123abc", 10); // mixed = 123Always specify the radix (second parameter) to avoid unexpected behavior:
// Good: explicit radix
parseInt("08", 10); // 8
// Bad: implicit radix (older JS versions might interpret as octal)
parseInt("08"); // Could be 0 in older environmentsFor strings containing decimal numbers, use parseFloat() to preserve the fractional part:
// Parse floating-point numbers
let temperature: number = parseFloat("98.6"); // temperature = 98.6
// Works with scientific notation
let scientific: number = parseFloat("1.23e-4"); // scientific = 0.000123
// Handles leading/trailing whitespace
let trimmed: number = parseFloat(" 3.14 "); // trimmed = 3.14Compare with parseInt():
parseInt("3.14", 10); // Returns 3 (integer only)
parseFloat("3.14"); // Returns 3.14 (preserves decimal)The unary plus (+) operator is a concise way to convert strings to numbers:
// Unary plus conversion
let value: number = +"123"; // value = 123
// Works with variables
const stringValue = "456";
let numValue: number = +stringValue; // numValue = 456
// Useful in inline expressions
const total: number = +price1 + +price2;This is especially useful when working with form inputs:
// React example
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
const formData = new FormData(event.currentTarget);
const age: number = +(formData.get("age") as string);
};
// Angular example
onSubmit() {
const quantity: number = +this.form.value.quantity;
}If your variable legitimately needs to handle both strings and numbers, use a union type instead of forcing conversion:
// Allow both types
let id: string | number;
id = "user-123"; // Valid
id = 123; // Also valid
// Function accepting either type
function formatId(id: string | number): string {
return `ID: ${id}`;
}
formatId("abc-123"); // Works
formatId(456); // Also worksUse type guards when you need type-specific operations:
function processValue(value: string | number) {
if (typeof value === "number") {
// TypeScript knows value is number here
return value.toFixed(2);
} else {
// TypeScript knows value is string here
return value.toUpperCase();
}
}When converting user-provided strings, always validate the result to handle invalid inputs gracefully:
function safeNumberConversion(input: string): number | null {
const num = Number(input);
// Check if conversion resulted in valid number
if (isNaN(num)) {
return null;
}
return num;
}
// Usage
const userInput = "123";
const result = safeNumberConversion(userInput);
if (result !== null) {
console.log("Valid number:", result);
} else {
console.error("Invalid number input");
}For more complex validation, consider using a library like Zod:
import { z } from "zod";
const NumberSchema = z.string().transform((val) => {
const num = Number(val);
if (isNaN(num)) {
throw new Error("Invalid number");
}
return num;
});
// Safe parsing
const parsed = NumberSchema.safeParse("123");
if (parsed.success) {
const value: number = parsed.data; // TypeScript knows this is a number
}Type Coercion vs Explicit Conversion: While JavaScript allows implicit type coercion (e.g., "5" * 2 = 10), TypeScript requires explicit conversions for assignments. This prevents subtle bugs where coercion might produce unexpected results.
Performance Considerations: The unary plus operator (+) and Number() constructor have similar performance, while parseInt() and parseFloat() are slightly slower due to string parsing logic. For most applications, the performance difference is negligible—choose based on readability and requirements.
Handling Edge Cases: Be aware that Number("") returns 0, Number(null) returns 0, and Number(undefined) returns NaN. If you need stricter validation, check for these cases explicitly:
function strictNumberConversion(input: string): number {
if (input.trim() === "") {
throw new Error("Empty string is not a valid number");
}
const num = Number(input);
if (isNaN(num)) {
throw new Error(`Invalid number: ${input}`);
}
return num;
}TypeScript Strict Mode: When using strictNullChecks, ensure your conversion functions account for potentially null or undefined inputs. Using the non-null assertion operator (!) bypasses TypeScript's safety checks and should be avoided unless you're absolutely certain the value exists.
Generic Type Conversion Function: For reusable type-safe conversions across your codebase, consider creating a generic converter:
function convertToNumber<T extends string | null | undefined>(
value: T
): T extends null | undefined ? null : number {
if (value == null) return null as any;
const num = Number(value);
if (isNaN(num)) throw new Error(`Cannot convert "${value}" to number`);
return num as any;
}Type parameter 'X' is not used in the function signature
How to fix "Type parameter not used in function signature" in TypeScript
Type parameter 'X' is defined but never used
How to fix "Type parameter is defined but never used" in TypeScript
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript