Skip to main content

TypeScript Integer Type

You're debugging a checkout flow where item.quantity * item.price returns 299.99999999999994 instead of 300. Or worse, you're parsing a user's age input and parseInt("08") silently returns 0 in some environments. These aren't rounding errors—they're the reality of working with numbers in JavaScript and TypeScript.

TypeScript doesn't have a dedicated integer type like Java's int or C#'s Int32. Everything numeric is either number (a 64-bit floating-point value) or bigint (for arbitrarily large integers). This means you need to validate and handle integers deliberately. This guide shows you how to work with integers safely in TypeScript, from validation patterns to conversion pitfalls that bite even experienced developers.

Defining an Integer Type in TypeScript

TypeScript doesn't have a built-in integer type, but you can create one using type guards or branded types. Here's how to enforce integer constraints at compile time and runtime.

Basic Type Guard Approach

function isInteger(value: number): boolean {
return Number.isInteger(value);
}

let userId: number = 10;
console.log(isInteger(userId)); // true

This works for runtime checks, but TypeScript won't prevent you from assigning 3.14 to userId at compile time. For stronger guarantees, use branded types.

Branded Types for Integer Enforcement

Branded types add a compile-time "brand" that distinguishes integers from regular numbers:

// Define a branded integer type
type Integer = number & { __brand: 'integer' };

function toInteger(value: number): Integer {
if (!Number.isInteger(value)) {
throw new Error(`Expected integer, got ${value}`);
}
return value as Integer;
}

// Now you can enforce integer constraints
function setPageSize(size: Integer): void {
console.log(`Page size set to ${size}`);
}

const pageSize = toInteger(20);
setPageSize(pageSize); // ✓ Works

// setPageSize(20); // ✗ Type error: number isn't assignable to Integer
// setPageSize(toInteger(20.5)); // ✗ Throws error at runtime

Practical Example: API Response Validation

Here's how branded integers help validate API responses where certain fields must be whole numbers:

type Integer = number & { __brand: 'integer' };
type PositiveInteger = Integer & { __positive: true };

function toInteger(value: number): Integer {
if (!Number.isInteger(value)) {
throw new Error(`Expected integer, got ${value}`);
}
return value as Integer;
}

function toPositiveInteger(value: number): PositiveInteger {
const int = toInteger(value);
if (int <= 0) {
throw new Error(`Expected positive integer, got ${int}`);
}
return int as PositiveInteger;
}

interface UserProfile {
id: PositiveInteger;
age: PositiveInteger;
loginCount: Integer; // Can be 0, but must be whole number
rating: number; // Can be decimal (e.g., 4.5)
}

// Validating API data
function parseUserProfile(data: any): UserProfile {
return {
id: toPositiveInteger(data.id),
age: toPositiveInteger(data.age),
loginCount: toInteger(data.loginCount),
rating: Number(data.rating), // Regular number, decimals OK
};
}

// This catches decimal values at the validation boundary
const profile = parseUserProfile({
id: 12345,
age: 28,
loginCount: 0,
rating: 4.7
});

This approach uses TypeScript type guards to ensure values are integers before using them. For complex validation scenarios, use TypeScript validation libraries like Zod, which integrate with Convex for database schema validation.

For type conversions, see TypeScript number to string and TypeScript string to number.

Performing Arithmetic Operations with Integers

Standard arithmetic operators work with integers, but watch out for division—it always returns a number, even if both operands are whole numbers:

let itemCount: number = 10;
let boxCapacity: number = 3;

// Basic operations
console.log(itemCount + boxCapacity); // 13
console.log(itemCount - boxCapacity); // 7
console.log(itemCount * boxCapacity); // 30
console.log(itemCount / boxCapacity); // 3.3333... (decimal result!)
console.log(itemCount % boxCapacity); // 1 (modulo operation)

// To ensure integer results from division
console.log(Math.floor(itemCount / boxCapacity)); // 3 (rounds down)
console.log(Math.trunc(itemCount / boxCapacity)); // 3 (truncates decimal part)
console.log(Math.round(itemCount / boxCapacity)); // 3 (rounds to nearest integer)

When working with integer arrays in TypeScript array operations, you'll often need to perform calculations on multiple values. The map function and reduce methods are useful for these operations:

const quantities: number[] = [1, 2, 3, 4, 5];

// Double each quantity
const doubled = quantities.map(q => q * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// Sum all quantities
const totalQuantity = quantities.reduce((acc, curr) => acc + curr, 0);
console.log(totalQuantity); // 15

For complex integer scenarios, explore TypeScript cast techniques. When building applications with database persistence, Convex functions handle integer validation at the database level.

Converting Strings to Integers: parseInt() vs Number() vs Unary Plus

Converting strings to integers is one of the most common operations you'll perform, especially when handling form data or API responses. TypeScript gives you three main options, and choosing the wrong one causes bugs.

parseInt(): Best for Extracting Integers from Mixed Strings

parseInt() parses a string and returns an integer, stopping at the first non-numeric character:

const pageNumber = parseInt("42", 10);     // 42
const fontSize = parseInt("16px", 10); // 16 (extracts the number)
const userId = parseInt("user123", 10); // NaN (no leading digits)

// ALWAYS specify radix 10 to avoid octal interpretation
parseInt("08", 10); // 8 (correct)
parseInt("08"); // 0 in some older environments (octal interpretation)

When to use parseInt(): You're scraping numbers from strings that contain other characters (like CSS values or user input with units), and you want to extract just the numeric portion.

Number(): Best for Strict Validation

Number() converts a string to a number but returns NaN if the entire string isn't a valid number:

const count = Number("42");        // 42
const price = Number("29.99"); // 29.99 (preserves decimals!)
const invalid = Number("42px"); // NaN (rejects mixed strings)
const empty = Number(""); // 0 (watch out for this!)

// More strict than parseInt
Number("3.14"); // 3.14
parseInt("3.14", 10); // 3 (truncates)

When to use Number(): You need strict validation and want NaN for anything that isn't a pure numeric string. But watch out—Number("") returns 0, which can be a gotcha when validating form fields.

Unary Plus (+): Shorthand for Number()

The unary plus operator is functionally identical to Number() but more concise:

const quantity = +"42";       // 42
const rating = +"4.5"; // 4.5
const bad = +"42px"; // NaN

// Equivalent to Number()
+"" === 0; // true
+null === 0; // true

When to use unary plus: You want Number() behavior but prefer terser syntax. Just be careful—+value can be harder to spot in code reviews than Number(value).

Practical Comparison

function parsePageNumber(input: string): number {
// parseInt extracts the number even from "page 5"
return parseInt(input, 10);
}

function validateAge(input: string): number | null {
const age = Number(input);

// Number() ensures strict validation
if (isNaN(age) || input.trim() === "") {
return null;
}

// Then validate it's an integer in a valid range
if (!Number.isInteger(age) || age < 0 || age > 150) {
return null;
}

return age;
}

console.log(parsePageNumber("5")); // 5
console.log(parsePageNumber("page 5")); // NaN

console.log(validateAge("25")); // 25
console.log(validateAge("25.5")); // null (not an integer)
console.log(validateAge("")); // null (empty string)

For more type conversion operations, see TypeScript string to number conversions. When working with user input in forms or API requests, convert strings to integers before validation. Convex validation functions ensure data integrity at the database level.

To convert numbers back to strings, see TypeScript number to string for formatting options.

Validating Integer Input

When validating integer input from forms, APIs, or user data, you need both type checking and value validation:

function validateIntegerInput(value: string): boolean {
const parsed = parseInt(value, 10);
return !isNaN(parsed) && Number.isInteger(parsed) && String(parsed) === value.trim();
}

console.log(validateIntegerInput("10")); // true
console.log(validateIntegerInput("10.5")); // false
console.log(validateIntegerInput("10px")); // false
console.log(validateIntegerInput("abc")); // false
console.log(validateIntegerInput(" 10 ")); // true

// More robust validation for specific ranges
function validateIntegerRange(value: string, min: number, max: number): boolean {
const parsed = parseInt(value, 10);
return !isNaN(parsed) && Number.isInteger(parsed) && parsed >= min && parsed <= max;
}

// Validate positive integers only
function validatePositiveInteger(value: string): boolean {
const parsed = parseInt(value, 10);
return !isNaN(parsed) && Number.isInteger(parsed) && parsed > 0;
}

For form validation, use Convex functions with validation to ensure data integrity at both client and server level. The TypeScript typeof operator helps with runtime type checking, while TypeScript string to number conversions are essential for processing user input.

When working with collections of integers, TypeScript array methods like every() and some() can help validate multiple values at once:

const stockQuantities: string[] = ["10", "25", "8", "42", "15"];
const allValidQuantities = stockQuantities.every(val => validateIntegerInput(val));
console.log(allValidQuantities); // true

Checking if a Number is an Integer

Number.isInteger() tells you if a value is a whole number:

// Basic integer checks
console.log(Number.isInteger(10)); // true
console.log(Number.isInteger(10.0)); // true (still an integer)
console.log(Number.isInteger(10.5)); // false
console.log(Number.isInteger("10")); // false (string)
console.log(Number.isInteger(NaN)); // false
console.log(Number.isInteger(Infinity)); // false

// Creating a type guard function
function isInteger(value: unknown): value is number {
return typeof value === 'number' && Number.isInteger(value);
}

// Using the type guard
const mixedData: unknown[] = [42, "100", 3.14, null, true];
const integerValues = mixedData.filter(isInteger);
console.log(integerValues); // [42]

// Custom validation for specific use cases
function isSafeInteger(value: number): boolean {
return Number.isInteger(value) &&
value >= Number.MIN_SAFE_INTEGER &&
value <= Number.MAX_SAFE_INTEGER;
}

The TypeScript typeof operator works with Number.isInteger() to create robust type guards. When working with arrays of values, TypeScript array methods like filter() help separate integers from other values.

For database operations, use TypeScript validation patterns in your Convex functions to ensure data integrity. Type checking is especially important when dealing with user input or external data sources.

Handling Integer Overflow

JavaScript and TypeScript use IEEE 754 double-precision floating-point numbers, which have limits for representing integers. Beyond these limits, precision is lost:

// Safe integer limits
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991

// Checking if an integer is safe
function isSafeInteger(value: number): boolean {
return Number.isSafeInteger(value);
}

// When precision is lost
const twitterId = Number.MAX_SAFE_INTEGER + 1;
console.log(twitterId); // 9007199254740992
console.log(twitterId + 1); // 9007199254740992 (no change!)

// Using BigInt for larger integers (like database IDs or timestamps)
const largeId = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1);
console.log(largeId.toString()); // "9007199254740992"

// Converting between Number and BigInt
const regularNumber = Number(BigInt(42));
const bigNumber = BigInt(42);

// Note: BigInt cannot be mixed with regular numbers in operations
// const invalid = largeId + 1; // TypeError
const valid = largeId + BigInt(1);

When working with large numbers in TypeScript applications, use Convex's TypeScript best practices to handle data integrity issues. For complex calculations that might exceed safe integer limits, explore TypeScript cast operations to work with BigInt values.

The BigInt type provides arbitrary-precision integers but requires careful handling when converting between number types. For database operations with large integers, Convex validation helps ensure values stay within safe ranges.

Using Integer-Specific Functions

The Math object provides several functions for working with integers:

// Rounding functions
const price: number = 10.7;
console.log(Math.floor(price)); // 10 (rounds down)
console.log(Math.ceil(price)); // 11 (rounds up)
console.log(Math.round(price)); // 11 (rounds to nearest)
console.log(Math.trunc(price)); // 10 (removes decimal)

// Working with absolute values
console.log(Math.abs(-42)); // 42

// Finding min/max in arrays
const scores: number[] = [85, 92, 78, 91, 88];
console.log(Math.min(...scores)); // 78
console.log(Math.max(...scores)); // 92

// Generating random integers
function randomInteger(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}

console.log(randomInteger(1, 100)); // Random integer between 1 and 100

// Converting to specific bases
const statusCode: number = 255;
console.log(statusCode.toString(16)); // "ff" (hexadecimal)
console.log(statusCode.toString(2)); // "11111111" (binary)

TypeScript array methods complement these Math functions. For data validation, Convex's validation system supports integer constraints and transformations.

For complex mathematical operations, explore TypeScript map operations to apply transformations across arrays of integers.

Common Integer Pitfalls and How to Avoid Them

Even experienced TypeScript developers run into these integer-related bugs. Here's what to watch for.

Pitfall #1: Forgetting the Radix in parseInt()

// Dangerous: radix defaults to 10, but can be octal in older environments
parseInt("08"); // Could be 0 in some browsers!

// Safe: always specify radix 10
parseInt("08", 10); // Always 8

Why it matters: Without the radix, parseInt() tries to guess the base. Strings starting with "0" can be interpreted as octal (base 8), where "08" is invalid and returns 0.

Pitfall #2: Number("") Returns 0, Not NaN

function getQuantity(input: string): number {
return Number(input);
}

getQuantity(""); // 0 (not NaN!)

The fix:

function getQuantity(input: string): number | null {
if (input.trim() === "") {
return null; // Explicit handling of empty strings
}

const value = Number(input);
return isNaN(value) ? null : value;
}

Pitfall #3: Not Checking for NaN After Conversion

// Dangerous: calculations with NaN silently fail
const age = parseInt("abc", 10); // NaN
const nextYear = age + 1; // NaN (no error thrown!)

// Safe: validate before using
const age = parseInt("abc", 10);
if (isNaN(age)) {
throw new Error("Invalid age");
}
const nextYear = age + 1; // Only runs if age is valid

Pitfall #4: Floating-Point Precision Errors

// Classic floating-point bug
0.1 + 0.2; // 0.30000000000000004

// When dealing with money, multiply to work with integers
const priceInCents = 1050; // $10.50
const taxInCents = 105; // $1.05
const totalInCents = priceInCents + taxInCents; // 1155 (exact!)
const totalInDollars = totalInCents / 100; // 11.55

Pitfall #5: parseInt() vs Number() Type Safety

// parseInt provides less type safety
const userInput = "42px";

parseInt(userInput, 10); // 42 (silently extracts number)
Number(userInput); // NaN (strict validation)

// For user input validation, Number() catches more errors
function validateInteger(input: string): number | null {
const num = Number(input);

if (isNaN(num) || !Number.isInteger(num)) {
return null;
}

return num;
}

Final Thoughts on TypeScript Integer Handling

Working with integers in TypeScript requires careful validation and awareness of JavaScript's number type limitations. Always validate user input, use type guards for runtime checks, and be mindful of safe integer limits. For values beyond Number.MAX_SAFE_INTEGER, consider using BigInt for arbitrary-precision arithmetic.

The key rules to remember:

  • Always specify radix 10 in parseInt()
  • Check for NaN after any string-to-number conversion
  • Use Number() for strict validation, parseInt() for extraction
  • Watch for empty string edge cases with Number()
  • Use branded types when you need compile-time integer guarantees