How to Explicitly Define a Class Property as a TypeScript Int Type
TypeScript does not have a dedicated int type; instead, you use the number primitive combined with branded types, literal unions, runtime validation, or bigint for arbitrary-precision integers.
TypeScript's type system is designed around JavaScript's runtime behavior, which treats all numeric values as IEEE 754 floating-point numbers. If you need to explicitly define a class property as a TypeScript int type, you must implement patterns that simulate nominal typing or enforce constraints at runtime. This guide examines the compiler's internal representation of numeric types in the microsoft/TypeScript repository and provides practical implementation strategies based on the source architecture.
Why TypeScript Lacks a Native Integer Type
The TypeScript compiler does not distinguish between integers and floating-point numbers at the type-checking level. In src/compiler/types.ts, the SyntaxKind enum defines numeric tokens using NumberKeyword, which represents the entire universe of JavaScript numbers. The public-facing type definition resides in src/lib/es5.d.ts at line 541, where the Number interface declares the primitive capabilities without separate integer variants.
Because the type checker treats all numeric literals and expressions as the number primitive, developers must layer integer constraints on top of this foundation using type-system patterns or runtime guards.
Four Methods to Enforce Integer Class Properties
When you need to ensure a class property holds only whole numbers, choose from these four approaches based on your performance, precision, and compile-time safety requirements.
Branded Types for Compile-Time Safety
Create a nominal type brand that distinguishes validated integers from generic numbers. This pattern relies on TypeScript's intersection types to add an invisible tag that prevents accidental assignment of unvalidated values.
type Int = number & { __brand: 'Int' };
function toInt(value: number): Int {
if (!Number.isInteger(value)) {
throw new Error('Value must be an integer');
}
return value as Int;
}
class Point {
public x: Int;
public y: Int;
constructor(x: number, y: number) {
this.x = toInt(x);
this.y = toInt(y);
}
}
The Int type is compatible with the underlying number primitive defined in src/lib/es5.d.ts, but the brand ensures only values processed through toInt() can be assigned to class properties.
Literal Unions for Bounded Ranges
For small, discrete sets of integers, define the property using a union of numeric literal types. This provides compile-time exhaustiveness checking without runtime overhead.
type SmallInt = 0 | 1 | 2 | 3 | 4 | 5;
class Counter {
private _value: SmallInt = 0;
increment(): void {
if (this._value < 5) {
this._value = (this._value + 1) as SmallInt;
}
}
get value(): SmallInt {
return this._value;
}
}
The compiler treats SmallInt as a distinct type, preventing assignments outside the defined literals while maintaining the JavaScript number runtime representation.
Runtime Validation with the number Type
Store the property as a standard number but enforce integer constraints in constructors and setters. This approach aligns with validation logic found in src/compiler/utilities.ts, where internal compiler functions check numeric properties at runtime.
class Age {
private _years: number;
constructor(years: number) {
this.setYears(years);
}
setYears(years: number): void {
if (!Number.isInteger(years) || years < 0) {
throw new Error('Age must be a non-negative integer');
}
this._years = years;
}
get years(): number {
return this._years;
}
}
This method offers flexibility when integer constraints are business logic requirements rather than type-system invariants.
Using bigint for Arbitrary-Precision Integers
When your class properties require integers beyond the safe limit of Number.MAX_SAFE_INTEGER (2^53-1), use the bigint primitive introduced in ES2020. The type definitions reside in src/lib/es2020.bigint.d.ts.
class LargeCounter {
private count: bigint = 0n;
increment(): void {
this.count += 1n;
}
get value(): bigint {
return this.count;
}
}
bigint provides true integer semantics for cryptography, large counters, and high-precision calculations, though it cannot be mixed with standard number types without explicit conversion.
Key Source Files in the TypeScript Compiler
Understanding how the compiler handles numeric values helps inform these patterns:
src/compiler/types.ts– DefinesSyntaxKind.NumberKeywordand the internal type flags used by the checker to identify numeric values.src/lib/es5.d.ts– Declares the publicNumberinterface andNumberConstructoravailable in user code, including static methods likeisInteger.src/lib/es2020.bigint.d.ts– Provides type declarations for thebigintprimitive and associated operations.src/compiler/utilities.ts– Contains helper functions for numeric validation and type narrowing used internally by the compiler.src/compiler/transformers/typeSerializer.ts– Handles the serialization of number-like types when generating declaration files.
These files demonstrate that TypeScript intentionally maintains a unified numeric type to match JavaScript semantics, leaving integer specialization to userland patterns.
Summary
- TypeScript uses the
numberprimitive for all numeric values; no separateinttype exists insrc/compiler/types.tsor the standard library. - Branded types create compile-time nominal types that prevent accidental assignment of non-integer numbers to class properties.
- Literal unions offer type-safe, bounded integer ranges without runtime performance costs.
- Runtime validation with
Number.isInteger()enforces integer constraints when type-system overhead is unnecessary. bigint, defined insrc/lib/es2020.bigint.d.ts, handles arbitrary-precision integers beyond the safe limits of standard numbers.
Frequently Asked Questions
Does TypeScript have a native int type?
No. According to the TypeScript source code in src/compiler/types.ts, numeric literals and expressions are categorized under SyntaxKind.NumberKeyword, which corresponds to the JavaScript number primitive. This primitive encompasses both integers and floating-point values without distinction.
How can I validate that a class property is an integer without affecting runtime performance?
Use a branded type pattern. Define type Int = number & { __brand: 'Int' } and provide a factory function that validates inputs before casting. The brand exists only at compile time and is erased during transpilation, ensuring zero runtime overhead while maintaining type safety.
When should I use bigint instead of number for class properties?
Use bigint when your properties must represent integers larger than Number.MAX_SAFE_INTEGER (2^53-1) or when you require arbitrary-precision arithmetic. The bigint type is declared in src/lib/es2020.bigint.d.ts and operates as a distinct primitive that cannot be implicitly converted to number.
Can I enforce integer constraints using only the type system without runtime checks?
For small, fixed ranges, yes. Use literal union types like type Byte = 0 | 1 | 2 | ... | 255 to restrict assignments at compile time. However, for unbounded integers, you must use branded types (which require at least one runtime validation point) or accept the base number type with runtime guards.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →