Skip to main content

How to Convert Numbers to Strings in TypeScript

You're building a product detail page and notice the price displays as [object Object] instead of "$29.99". Or maybe you're constructing a URL with a numeric ID, but it's failing because you forgot to convert it to a string first. These bugs happen all the time—and they're usually easy to fix once you know the right conversion method to use.

TypeScript gives you several ways to convert numbers to strings, each with different trade-offs. Some handle edge cases better, others offer more formatting control, and a few are optimized for performance. In this guide, you'll learn when to use each method and how to avoid common pitfalls when converting numeric values to strings.

Converting Numbers to Strings in TypeScript

In TypeScript, you can convert numbers to strings using several methods, each with its own advantages:

let productId: number = 10;

// Method 1: Using toString()
let str1: string = productId.toString();

// Method 2: Using String() constructor
let str2: string = String(productId);

// Method 3: Using template literals
let str3: string = `${productId}`;

console.log(str1, str2, str3); // Outputs: "10 10 10"

Each method has its advantages. The toString() method is straightforward and performant for converting a number you know exists. The String() function is more defensive—it won't throw errors on null or undefined values. Template literals provide a clean syntax for embedding numbers within strings, especially useful when working with string interpolation.

When building full-stack TypeScript apps with Convex, you'll often convert numeric IDs or timestamps to strings when constructing URLs or preparing data for display after fetching from your database queries.

When to Choose Which Method

Here's how to decide which conversion method to use based on your specific scenario:

// Use toString() when you're certain the value is a number
let userId: number = 42;
let userUrl = `/users/${userId.toString()}`;

// Use String() when the value might be null or undefined
function formatValue(value: number | null): string {
return String(value); // Returns "null" instead of throwing an error
}

// Use template literals when embedding numbers in longer strings
let price: number = 29.99;
let message = `Your total is $${price}`;

// AVOID: String concatenation with +
let avoidThis = num + ""; // Works but less clear than other methods

Key difference: The toString() method will throw a TypeError if called on null or undefined, while String() safely converts these values to "null" and "undefined" strings. When working with data from external sources like API responses, String() provides safer handling.

When working with complex data structures in TypeScript, the typeof operator can help verify successful type conversions.

Converting Numeric Values to Strings

You can convert various numeric values to strings, including integers, floating-point numbers, and special numeric values:

// Converting integers
let orderCount: number = 42;
let orderCountString: string = orderCount.toString();

// Converting floating-point numbers
let piValue: number = 3.14159;
let piString: string = piValue.toString();

// Converting with specific decimal places using toFixed()
let productPrice: number = 19.5;
let priceDisplay: string = productPrice.toFixed(2); // "19.50"

// Converting scientific notation
let largeNumber: number = 5e6;
let largeNumberString: string = largeNumber.toString(); // "5000000"

console.log(orderCountString, piString, priceDisplay, largeNumberString);

The toFixed() method is particularly useful when you need to format numbers with a specific number of decimal places, such as when displaying currency values or ensuring consistent formatting.

When working with utility types and Convex schemas, these conversion techniques ensure proper data formatting before storage and display.

Formatting Numbers with Precision Control

TypeScript provides additional methods for controlling how numbers are represented as strings, especially useful for scientific or financial applications:

// Using toPrecision() - controls total significant digits
let measurement: number = 123.456;
let preciseString = measurement.toPrecision(4); // "123.5"
let scientificString = measurement.toPrecision(2); // "1.2e+2"

// Using toExponential() - always returns scientific notation
let largeValue: number = 12345.6789;
let exponential = largeValue.toExponential(2); // "1.23e+4"
let moreDecimal = largeValue.toExponential(4); // "1.2346e+4"

// Comparing the three precision methods
let num: number = 5.6789;
console.log(num.toFixed(2)); // "5.68" - fixed decimal places
console.log(num.toPrecision(3)); // "5.68" - significant digits
console.log(num.toExponential(2)); // "5.68e+0" - scientific notation

When to use each method:

  • toFixed(): Use when you need a consistent number of decimal places (like currency: $5.00)
  • toPrecision(): Use when you care about significant digits regardless of where the decimal falls (like scientific measurements)
  • toExponential(): Use when you need to represent very large or very small numbers in compact form (like 1.23e+10)

These formatting methods are particularly useful when preparing numeric data from Convex database queries for display in charts, dashboards, or scientific applications where precision matters.

Handling Edge Cases During Conversion

When converting numbers from untrusted sources like API responses or user input, you'll encounter special numeric values that need careful handling:

// Converting NaN - check before displaying to users
let result: number = 0 / 0; // NaN
let resultString: string = String(result); // "NaN"

// Better: validate before converting
function safeNumberToString(value: number): string {
if (Number.isNaN(value)) {
return "Invalid number";
}
return value.toString();
}

// Converting Infinity
let tooLarge: number = 1e308 * 2; // Infinity
let infinityString: string = tooLarge.toString(); // "Infinity"

// Converting negative zero (rare but possible)
let negativeZero: number = -0;
let negativeZeroString: string = String(negativeZero); // "0"

// Very large numbers use scientific notation automatically
let largeNumber: number = 1e21;
let largeNumberString: string = largeNumber.toString(); // "1e+21"

Why this matters: If you're fetching numeric data from a Convex database or external API and directly displaying it, you don't want users seeing "NaN" or "Infinity" on your page. Always validate and handle these cases before converting to strings for display.

Maintaining Consistent Type Handling

When working with functions that accept multiple types, use type guards to handle conversions safely:

function formatDisplayValue(value: number | string): string {
// Type guard to check if value is a number
if (typeof value === 'number') {
return value.toString();
}

// Value is already a string
return value;
}

console.log(formatDisplayValue(42)); // "42"
console.log(formatDisplayValue("hello")); // "hello"

// More robust example for API data
interface ProductData {
id: number | string;
price: number;
}

function buildProductUrl(product: ProductData): string {
// Ensure ID is always a string for URL
const productId = typeof product.id === 'number'
? product.id.toString()
: product.id;

return `/products/${productId}`;
}

Type guards prevent runtime errors and make your conversion logic explicit. This is especially useful when working with data from Convex mutations and queries, where you want to ensure consistent data types flow between your database, backend functions, and frontend components.

Converting Numbers to Different Bases

The toString() method accepts an optional base parameter (radix) for converting numbers to different number systems. This is useful for working with hexadecimal colors, binary flags, or octal permissions:

let colorValue: number = 255;

// Decimal (base 10) - default
let decimal: string = colorValue.toString(); // "255"

// Hexadecimal (base 16) - common for colors
let hex: string = colorValue.toString(16); // "ff"
let hexColor: string = `#${hex}${hex}${hex}`; // "#ffffff" (white)

// Binary (base 2) - useful for bit flags
let binary: string = colorValue.toString(2); // "11111111"

// Octal (base 8) - sometimes used for file permissions
let octal: string = colorValue.toString(8); // "377"

// You can convert to any base from 2 to 36
let base36: string = (1234567890).toString(36); // "kf12oi"

Real-world use cases:

  • Hexadecimal: Converting RGB values to hex color codes (rgb(255, 99, 71)#ff6347)
  • Binary: Representing feature flags or permissions (user permissions: 0b1010 = read + execute)
  • Base36: Creating short, URL-safe identifiers from numeric IDs

When building applications with Convex, you might store numeric color values or permission flags in your database and convert them to appropriate string representations for your frontend.

Formatting Numbers with Locale Support

When displaying numbers to users in different regions, TypeScript provides two approaches: toLocaleString() for quick formatting and Intl.NumberFormat for better performance when formatting multiple values:

let price: number = 1234.56;

// Method 1: toLocaleString() - simple but slower for multiple values
let usPrice: string = price.toLocaleString('en-US', {
style: 'currency',
currency: 'USD'
}); // "$1,234.56"

let euroPrice: string = price.toLocaleString('de-DE', {
style: 'currency',
currency: 'EUR'
}); // "1.234,56 €"

// Method 2: Intl.NumberFormat - faster for formatting many numbers
const currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});

// Reuse the formatter for better performance
let formattedPrice1 = currencyFormatter.format(1234.56); // "$1,234.56"
let formattedPrice2 = currencyFormatter.format(9876.54); // "$9,876.54"

// Format percentages
const percentFormatter = new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2
});
console.log(percentFormatter.format(0.1234)); // "12.34%"

Performance tip: When formatting multiple numbers (like a list of products with prices), Intl.NumberFormat is approximately 70x faster than calling toLocaleString() repeatedly. Create the formatter once and reuse it.

This is particularly useful when rendering data from Convex queries in your UI—format a single product price with toLocaleString(), but use Intl.NumberFormat when displaying a catalog of hundreds of products.

Common Challenges and Solutions

Here are real-world formatting challenges you'll encounter and how to solve them:

// Challenge: Preserving leading zeros (order numbers, IDs)
function formatOrderNumber(orderNum: number): string {
return orderNum.toString().padStart(6, '0');
}
console.log(formatOrderNumber(42)); // "000042"
console.log(formatOrderNumber(12345)); // "012345"

// Challenge: Consistent decimal precision (currency)
function formatPrice(amount: number): string {
return amount.toFixed(2);
}
console.log(formatPrice(10.1)); // "10.10"
console.log(formatPrice(99.999)); // "100.00" (rounds up)

// Challenge: Thousands separators for readability
function formatViewCount(views: number): string {
return views.toLocaleString('en-US');
}
console.log(formatViewCount(1000000)); // "1,000,000"

// Challenge: Preventing floating-point display issues
let subtotal = 0.1 + 0.2; // 0.30000000000000004
console.log(subtotal.toString()); // "0.30000000000000004" ❌
console.log(subtotal.toFixed(2)); // "0.30" ✅

These patterns are especially useful when displaying data from Convex queries—whether you're formatting view counts, order numbers, or prices, consistent string formatting improves the user experience and prevents confusing displays.

Final Thoughts on TypeScript Number to String Conversion

Converting numbers to strings in TypeScript comes down to choosing the right method for your situation. Use toString() when you know the value is a number and need the best performance. Reach for String() when handling values that might be null or undefined. Use template literals when embedding numbers in larger strings. And for formatting multiple values with locale support, Intl.NumberFormat beats toLocaleString() in performance.

Quick reference:

  • Simple conversion: num.toString()
  • Null-safe conversion: String(num)
  • Decimal places: num.toFixed(2)
  • Significant digits: num.toPrecision(3)
  • Different bases: num.toString(16) for hex
  • Currency formatting: Intl.NumberFormat for multiple values

Handle edge cases like NaN and Infinity explicitly, and leverage TypeScript's type system to catch conversion issues at compile time rather than in production. Whether you're building full-stack apps with Convex or working with React TypeScript frontends, these techniques will help you format numeric data reliably and efficiently.