../

Numbers

number, bigint and typed arrays in TypeScript 7.0 (also checked with 6.0) under strict, with ES2025 built-ins: literals, division and remainder, the 2⁵³ limit and BigInt, 32-bit bit operators, float traps, Math and number theory, modular arithmetic, parsing and formatting, typed arrays, costs, and the integer idioms DS&A solutions are built from. Every example runs on Node 24 LTS, Node 26 and Bun 1.4. The Python side is in Python numbers, mapped in Coming from Python.

Types & literals

TypeLiteralsNotes
number42, -7, 3.14, 1e9, 1_000_000, 0xff, 0o17, 0b1011one IEEE 754 double for integers and fractions alike
bigint42n, 2n ** 64n, BigInt("9007199254740993")exact, unbounded integers; never mixes with number
special valuesInfinity, -Infinity, NaN, -0all of type number
literal types0 | 1, -1, 10na type that holds exactly those values
Number, BigIntwrapper objects and conversion functionsannotate with number and bigint, never Number
typed arraysnew Int32Array(n), new Float64Array(n)fixed-size arrays of raw machine numbers (Typed arrays)
[typeof 42, typeof 4.2, typeof 42n];
// ["number", "number", "bigint"]
1e9 === 1_000_000_000; // true: 1e9 is an integer here
Number.isInteger(5.0); // true: 5.0 and 5 are one value
0.1 + 0.2; // 0.30000000000000004
2n ** 64n; // 18446744073709551616n
Number.MAX_SAFE_INTEGER; // 9007199254740991

There is no integer type. An integer is a number whose fraction is zero, and every integer up to 2⁵³ is stored exactly, so 1e9 is the integer 1 000 000 000 (in Python it would be a float). V8 keeps small integers in the value word itself ("Smis") instead of boxing them as heap doubles; that is an implementation detail that changes speed, never a result.

Arithmetic & division

ExpressionResultRule
7 / 2, 6 / 33.5, 2/ is real division; there is no integer-division operator
Math.trunc(-7 / 2)-3toward 0, like / on ints in C and Java
Math.floor(-7 / 2)-4toward −∞, like Python's //
-7 % 2, 7 % -2-1, 1% is a remainder: it takes the dividend's sign
((-7 % 2) + 2) % 21floored mod, always in [0, m) for m > 0
2 ** 10, 2 ** -11024, 0.5
-2 ** 2SyntaxErrorwrite (-2) ** 2 or -(2 ** 2); TypeScript reports TS17006
2 ** 3 ** 2512** is right-associative: 2 ** 9
Math.round(2.5), Math.round(-2.5)3, -2ties go toward +∞: not to even, not away from 0
Math.round(-0.4)-0prints as 0; Object.is tells them apart
(2.5).toFixed(0), (-2.5).toFixed(0)"3", "-3"a string; ties go away from 0, judged on the stored value
(1.005).toFixed(2)"1.00"1.005 is stored as 1.00499999999999989…
Math.abs(-5), Math.sign(-3)5, -1
Math.max(3, 7), Math.max()7, -InfinityMath.max(1, NaN) is NaN
const a = -7;
const b = 2;
[Math.trunc(a / b), Math.floor(a / b)]; // [-3, -4]
[a % b, ((a % b) + b) % b]; // [-1, 1]
 
// floored mod: the result takes the divisor's sign
const mod = (x: number, m: number): number =>
  ((x % m) + m) % m;
mod(-1, 5); // 4: index -1 wraps to the last slot
mod(12, 5); // 2
 
// ceiling division of non-negative integers
const ceilDiv = (x: number, y: number): number =>
  Math.floor((x + y - 1) / y);
ceilDiv(7, 2); // 4
-8 -7 -6 -5 -4 -3 -2 -1 0 a = -7 % and Math.trunc: toward 0 floored mod and Math.floor: toward -Infinity -7 / 2 = -3.5 Math.trunc → -3 -3 * 2 = -6 -7 % 2 = -7 - (-6) = -1 Math.floor → -4 -4 * 2 = -8 mod = -7 - (-8) = 1 = ((-7 % 2) + 2) % 2 -7 === -3 * 2 + (-1) trunc and %: so -7 % 2 === 1 is false -7 === -4 * 2 + 1 floor and floored mod

1/6

-7 / 2 is -3.5, between -4 and -3. Integer division has to pick one of them.

For safe integers a and b, Math.floor(a / b) and Math.trunc(a / b) are the exact integer quotient: the distance from a / b to the nearest integer is at least 1 / |b|, more than the rounding error of the division. Past 2⁵³ use BigInt, whose / truncates.

Safe integers & BigInt

FactValue or rule
Number.MAX_SAFE_INTEGER2 ** 53 - 1 = 9007199254740991, the last n with n and n + 1 both exact
Number.isSafeInteger(x)an integer within ±(2⁵³ − 1)
past 2⁵³doubles skip integers: 2 ** 53 + 1 === 2 ** 53
a * b with a, b near 10⁹about 10¹⁸, far past 2⁵³: silently rounded
10n, BigInt(10), BigInt("123")exact; BigInt(1.5) throws RangeError
1n + 1TypeError at run time, TS2365 at compile time: convert one side
-7n / 2n, -7n % 2n-3n, -1n: truncates like Math.trunc
Number(big)the nearest double: Number(2n ** 64n) is 18446744073709552000
BigInt.asIntN(32, x), BigInt.asUintN(64, x)wrap to a signed or unsigned width
comparisons1n < 2 and 1n == 1 work; 1n === 1 is false (different types)
Math.*, JSON.stringify, >>>reject bigint with a TypeError
const P = 2 ** 53;
P + 1 === P; // true
Number.isSafeInteger(P - 1); // true
Number.isSafeInteger(P); // false
 
const MOD = 1_000_000_007;
const x = 999_999_999;
(x * x) % MOD; // 63: x * x was rounded
Number((BigInt(x) * BigInt(x)) % BigInt(MOD)); // 64
 
BigInt.asIntN(32, 2n ** 31n); // -2147483648n
BigInt.asUintN(32, -1n); // 4294967295n
-7n / 2n; // -3n
integers near 2**53 = 9007199254740992 (ticks are offsets from it) -3 -2 -1 2**53 +1 +2 +3 +4 gap 1 MAX_SAFE_INTEGER gap 2 +1 and +3 are not doubles 2**53 + 1 → 2**53 2**53 + 3 → 2**53 + 4 n and n + 1 both exact: the last such n 999999999 * 999999999 exact: 999999998000000001 double: 999999998000000000 (the gap near 2**60 is 128) % 1_000_000_007 → 63, but the right answer is 64

1/6

Below 2**53 every integer is a double, one apart. Number.MAX_SAFE_INTEGER is 2**53 - 1 = 9007199254740991.

const n = 10n;
try {
  // @ts-expect-error: TS2365, bigint + number
  n + 1;
} catch (e) {
  console.log((e as Error).name); // "TypeError"
}
n + 1n; // 11n
Number(n) + 1; // 11

BigInt arithmetic allocates a new value per operation. For modular products of numbers below 10⁹ that costs less than it sounds: 5 million BigInt products took about 1.5× as long as the split mulmod in Modular arithmetic on Node 24. Pick BigInt for correctness first.

Bits

OperatorMeaningTypeScript detail
a & b, a | b, a ^ band, or, xorboth sides go through ToInt32 first: the result is a signed 32-bit integer
~xnot-x - 1 within 32 bits: ~5 === -6
x << kshift leftthe count is taken mod 32: 1 << 31 === -2147483648, 1 << 32 === 1
x >> karithmetic shift rightkeeps the sign: -7 >> 1 === -4
x >>> klogical shift rightunsigned 32-bit result: -1 >>> 0 === 4294967295
x | 0, ~~xtruncate to int32only while |x| < 2 ** 31: 1e10 | 0 === 1410065408
Math.clz32(x)leading zero bits of the 32-bit value31 - Math.clz32(n) is ⌊log₂ n⌋ for 0 < n < 2 ** 32
Math.imul(a, b)the exact low 32 bits of a * ba * b | 0 goes wrong once a * b passes 2⁵³
x.toString(2)binary textsign and magnitude: (-5).toString(2) === "-101"; (-5 >>> 0).toString(2) shows the bits
parseInt(s, 2)parse binaryparseInt("1011", 2) === 11
popcountnot built inthe v &= v - 1 loop or the SWAR version below
bigint bits&, |, ^, ~, <<, >>any width; >>> throws TypeError

&, | and ^ bind more loosely than ===, so n & 1 === 0 means n & (1 === 0). JavaScript quietly computes n & 0, which is always 0; TypeScript rejects it (TS2363 and TS2367). Write (n & 1) === 0.

const x = 12;
x & (x - 1); // 8: lowest set bit cleared
x & -x; // 4: lowest set bit alone
1 << 31; // -2147483648
1 << 32; // 1: the count is 32 % 32 = 0
-1 >>> 0; // 4294967295
(-5 >>> 0).toString(2).slice(-8); // "11111011"
31 - Math.clz32(40); // 5: floor(log2(40))
Math.imul(123456789, 987654321); // -67153019
(123456789 * 987654321) | 0; // -67153024: product rounded
33 32 31 16 0 bit ToInt32 keeps bits 31..0; bit 31 is the sign lost 3.7 | 0 truncate to 3, keep 32 bits → 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 (2 ** 32 + 5) | 0 bit 32 is dropped → 5 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 2 ** 31 | 0 and 1 << 31 bit 31 set: the sign → -2147483648 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 | 0 and -1 >>> 0 all 32 set → -1 signed, 4294967295 unsigned 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1e10 | 0 bits 33 and 32 dropped → 1410065408 1 0 0 1 0 1 0 1 0 0 0 0 0 0 1 0 1 1 1 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 1 << 32 shift count 32 & 31 = 0 → 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

1/6

x | 0 runs ToInt32: drop the fraction toward 0, keep the low 32 bits, then read bit 31 as the sign. 3.7 | 0 is 3.

The tricks themselves (lowest set bit, submasks, xor cancellation, popcount DP) are in Bit manipulation. Two of them, bit by bit, then popcount:

function popcount(n: number): number {
  let count = 0;
  // >>> 0 so negative numbers count their 32 bits
  for (let v = n >>> 0; v !== 0; v &= v - 1) count++;
  return count;
}
 
// the same with no loop: add bits in pairs, nibbles, bytes
function popcount32(x: number): number {
  x -= (x >>> 1) & 0x55555555;
  x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
  x = (x + (x >>> 4)) & 0x0f0f0f0f;
  return Math.imul(x, 0x01010101) >>> 24;
}
 
[popcount(13), popcount(-1), popcount32(255)]; // [3, 32, 8]
7 6 5 4 3 2 1 0 bit x = 12 0 0 0 0 1 1 0 0 x = 12 0 0 0 0 1 1 0 0 x - 1 = 11 0 0 0 0 1 0 1 1 x = 12 0 0 0 0 1 1 0 0 x - 1 = 11 0 0 0 0 1 0 1 1 x & (x - 1) = 8 0 0 0 0 1 0 0 0 & x = 12 0 0 0 0 1 1 0 0 -x = -12 1 1 1 1 0 1 0 0 x = 12 0 0 0 0 1 1 0 0 -x = -12 1 1 1 1 0 1 0 0 x & -x = 4 0 0 0 0 0 1 0 0 & n = 13 0 0 0 0 1 1 0 1 n - 1 = 12 0 0 0 0 1 1 0 0 n & (n - 1) = 12 0 0 0 0 1 1 0 0 & count = 1 n = 12 0 0 0 0 1 1 0 0 n - 1 = 11 0 0 0 0 1 0 1 1 n & (n - 1) = 8 0 0 0 0 1 0 0 0 & count = 2 n = 8 0 0 0 0 1 0 0 0 n - 1 = 7 0 0 0 0 0 1 1 1 n & (n - 1) = 0 0 0 0 0 0 0 0 0 & count = 3 n = 0 0 0 0 0 0 0 0 0 count = 3 (low 8 of 32 bits) loop ends lowest set bit differs from the top row 1 in the result

1/9

x = 12 is 0b1100. Its lowest set bit is bit 2.

Bitmasks over n items enumerate every subset, as long as 1 << n stays positive (n up to 30):

const items = ["a", "b", "c"];
const subsets: string[][] = [];
for (let mask = 0; mask < 1 << items.length; mask++) {
  subsets.push(items.filter((_, i) => (mask >> i) & 1));
}
subsets.length; // 8
subsets[5]; // ["a", "c"]: mask 0b101

Floats

A number is an IEEE 754 double: a sign bit, 11 exponent bits and 52 fraction bits with a hidden leading 1, so integers up to 2⁵³ are exact and most decimal fractions are not.

(0.1).toString(2).slice(0, 22);
// "0.00011001100110011001"
0.1 + 0.2 === 0.3; // false
Math.abs(0.1 + 0.2 - 0.3) < 1e-9; // true
(1.005).toPrecision(20); // "1.0049999999999998934"
0.1 as a 64-bit double (IEEE 754 binary64) sign exponent (11 bits) fraction (52 bits) 0 01111111011 1001100110011001100110011001100110011001100110011010 + 1019 - 1023 = -4 plus a hidden leading 1: 53 bits value = 1.1001100110011…1010 (binary) × 2**-4 = 0.100000000000000005551115123125782702… 0.1 has no finite binary form, so the nearest double is stored Number.EPSILON 2**-52, the gap between 1 and the next double Number.MAX_SAFE_INTEGER 2**53 - 1 = 9007199254740991 Number.MAX_VALUE about 1.8e308; past it you get Infinity Number.MIN_VALUE 5e-324, the smallest positive double
0.1 is stored as the nearest double, slightly above 0.1
ExpressionResultWhy
0.1 + 0.20.30000000000000004neither 0.1 nor 0.2 is exact in binary
Math.abs(a - b) < 1e-9tolerance testpick the tolerance from the problem's scale
Number.EPSILON2.220446049250313e-162⁻⁵², the gap after 1: too small as a tolerance for values far from 1
1e9 + 0.1 - 1e90.10000002384185791near 10⁹ the gap between doubles is about 1.2e-7
10 ** 16 / 3 === (10 ** 16 + 1) / 3truetwo different quotients, one double
NaN === NaNfalsetest with Number.isNaN(x)
isNaN("abc"), Number.isNaN("abc")true, falsethe global isNaN converts first
[NaN].includes(NaN), [NaN].indexOf(NaN)true, -1includes, Set and Map use SameValueZero; indexOf uses ===
0 === -0, Object.is(0, -0)true, false1 / -0 is -Infinity
0 / 0, Infinity - InfinityNaN1 / 0 is Infinity: division never throws
Number.MAX_VALUE * 2InfinityNumber.MAX_VALUE is 1.7976931348623157e+308
Number.MIN_VALUE5e-324the smallest positive double, not the most negative number
ten 0.1s added with reduce0.9999999999999999+ rounds at every step
Math.fround(0.1)0.10000000149011612the nearest 32-bit float: what a Float32Array stores

Math.sumPrecise(xs) adds without intermediate rounding, but of the three runtimes here only Bun 1.4 has it, and the TypeScript 6.0 and 7.0 libraries don't declare it yet.

Binary search over floats should loop a fixed number of times. while (hi - lo > eps) never ends when eps is smaller than the gap between adjacent doubles at that magnitude:

function sqrtBisect(v: number): number {
  let lo = 0;
  let hi = Math.max(1, v);
  for (let i = 0; i < 100; i++) {
    const mid = (lo + hi) / 2;
    if (mid * mid < v) lo = mid;
    else hi = mid;
  }
  return hi;
}
sqrtBisect(2) === Math.SQRT2; // true

Math & number theory

CallResultNotes
Math.floor, Math.ceil, Math.trunc, Math.rounda numberthey can return -0
Math.sqrt(n)correctly roundedMath.floor(Math.sqrt(n)) is the exact integer root for n < 2 ** 52, then can be one too high
Math.cbrt(27), Math.hypot(3, 4)3, 5
Math.log2(8), Math.log10(1000)3, 3Math.log(1000) / Math.LN10 is 2.9999999999999996
Math.floor(Math.log2(2 ** 50 - 1))50wrong (it is 49): use n.toString(2).length - 1
Math.floor(Math.log10(999999999999999))15wrong (it is 14): count digits with String(n).length
gcd, lcm, isqrt, modpow, nCrnot built ina few lines each (below and in Recipes)
Math.max(...xs), Math.min(...xs)RangeError on huge arraysthe limit depends on stack space: about 125 000 arguments on Node 24 and 26 and 640 000 on Bun 1.4 in a test here
function gcd(a: number, b: number): number {
  while (b !== 0) [a, b] = [b, a % b];
  return Math.abs(a);
}
 
const lcm = (a: number, b: number): number =>
  (a / gcd(a, b)) * b; // divide first: smaller product
 
// exact floor(sqrt(n)) for any safe integer n
function isqrt(n: number): number {
  let r = Math.floor(Math.sqrt(n));
  while (r * r > n) r--;
  while ((r + 1) * (r + 1) <= n) r++;
  return r;
}
 
[gcd(12, 18), gcd(0, 5), gcd(-4, 6)]; // [6, 5, 2]
lcm(4, 6); // 12
Math.floor(Math.sqrt(4503599761588224)); // 67108865
isqrt(4503599761588224); // 67108864

4503599761588224 is 67108865 ** 2 - 1, the smallest number where the plain Math.sqrt floor is off. lcm can pass 2⁵³ even when its inputs don't; switch to bigint there.

Modular arithmetic

"Return the answer modulo 10⁹ + 7" keeps answers small, but in TypeScript the product of two residues still reaches 10¹⁸, past the 2⁵³ that doubles hold exactly.

RuleTypeScript
sum(a + b) % MOD: below 2⁵³, exact
difference((a - b) % MOD + MOD) % MOD: % can return a negative
productmulmod(a, b, MOD) below, or BigInt
powermodPow(b, e, MOD); b ** e % MOD rounds, or overflows to Infinity
division by bmultiply by modPow(b, MOD - 2, MOD) (Fermat, prime MOD)
constantconst MOD = 1_000_000_007; (1e9 + 7 is the same exact integer)
const MOD = 1_000_000_007;
 
// exact a * b % m for 0 <= a, b < m < 2 ** 30
function mulmod(a: number, b: number, m: number): number {
  const hi = (a * (b >>> 16)) % m; // high part of b
  return (hi * 65536 + a * (b & 65535)) % m;
}
 
function modPow(b: number, e: number, m: number): number {
  let result = 1;
  b %= m;
  // Math.floor(e / 2), not e >> 1: e can pass 2 ** 31
  for (; e > 0; e = Math.floor(e / 2)) {
    if (e % 2 === 1) result = mulmod(result, b, m);
    b = mulmod(b, b, m);
  }
  return result;
}
 
mulmod(999_999_999, 999_999_999, MOD); // 64
modPow(2, 1e18, MOD); // 719476260
const inv3 = modPow(3, MOD - 2, MOD);
mulmod(6, inv3, MOD); // 2: "6 / 3" mod p
((3 - 5) % MOD + MOD) % MOD; // 1000000005

Every intermediate in mulmod stays below 2⁴⁷: a * (b >>> 16) is under 2⁴⁴, hi * 65536 and a * (b & 65535) under 2⁴⁶ each. The BigInt version is shorter and works for any modulus:

function modPowBig(b: bigint, e: bigint, m: bigint): bigint {
  let result = 1n;
  b %= m;
  for (; e > 0n; e >>= 1n) {
    if (e & 1n) result = (result * b) % m;
    b = (b * b) % m;
  }
  return result;
}
modPowBig(2n, 10n ** 18n, 1_000_000_007n); // 719476260n

Counting DP with large answers (paths, ways to climb, subsets) reduces every table cell; sums of two residues never need mulmod, products always do.

Parsing & formatting

CallResultNotes
Number("42"), +"42", Number(" 42 ")42the whole string must be a number; surrounding whitespace is fine
Number(""), Number(" "), Number(null)0a classic bug: check for empty input first
Number("12px"), Number(undefined), Number("1_000")NaNseparators are for literals only
Number("0x1f"), Number("0b101"), Number("1e3")31, 5, 1000
parseInt("12px", 10), parseInt(" -42xyz", 10)12, -42stops at the first bad character
parseInt("ff", 16), parseInt("z", 36)255, 35radix 2 to 36; always pass it
parseInt(0.0000005)5the argument becomes the string "5e-7" first
["1", "2", "3"].map(parseInt)[1, NaN, NaN]map passes the index as the radix: use .map(Number)
parseFloat("3.14abc")3.14
(255).toString(2), (255).toString(16)"11111111", "ff"any radix 2 to 36
(5).toString(2).padStart(8, "0")"00000101"fixed width
(1234.5678).toFixed(2), (1234.5678).toPrecision(6)"1234.57", "1234.57"strings, rounded
String(1e21), String(1e-7)"1e+21", "1e-7"exponent form from 10²¹ up and below 10⁻⁶
new Intl.NumberFormat("en-US").format(1234567.891)"1,234,567.891"grouping; also style: "percent", notation: "compact"
const n = 9045;
[...String(n)].map(Number); // [9, 0, 4, 5]
 
const digits: number[] = [];
for (let m = n; m > 0; m = Math.floor(m / 10)) {
  digits.push(m % 10); // least significant first
}
digits; // [5, 4, 0, 9]
digits.reduce((s, d) => s + d, 0); // 18
Number([9, 0, 4, 5].join("")); // 9045
["1", "2", "3"].map(Number); // [1, 2, 3]

Typed arrays

TypeElementStoring a value x
Int32Arraysigned 32-bitToInt32: 2 ** 31 becomes -2147483648, 1.9 becomes 1, -1.9 becomes -1
Uint32Arrayunsigned 32-bitmodulo 2³²
Uint8Arrayunsigned 8-bitmodulo 256: 256 becomes 0, -1 becomes 255
Uint8ClampedArray8-bit, clamped300 becomes 255, -5 becomes 0, 1.5 becomes 2, 2.5 becomes 2 (ties to even)
Float64Arraydoublean exact copy of a number
Float32Array32-bit floatMath.fround(x)
Float16Array16-bit floatnew in ES2025; Node 24+ and Bun
BigInt64Array, BigUint64Array64-bit integerbigint values only

A typed array has a fixed length, starts zero-filled, sorts numerically by default, and has no push. subarray returns a view on the same memory; slice copies. A million small integers took about 8 MB of heap as a plain array and 4 MB as an Int32Array on Node 24.

const counts = new Int32Array(26); // zero-filled
for (const ch of "banana") counts[ch.charCodeAt(0) - 97]++;
[counts[0], counts[1], counts[13]]; // [3, 1, 2]
 
new Int32Array([10, 9, 1, 2]).sort(); // [1, 2, 9, 10]
[10, 9, 1, 2].sort(); // [1, 10, 2, 9]: string order
 
const cell = new Int32Array(1);
cell[0] = 2 ** 31; // stored through ToInt32
cell[0]; // -2147483648
 
const buf = new Uint8Array([1, 2, 3, 4]);
const view = buf.subarray(1, 3); // shares memory
view[0] = 9;
buf; // [1, 9, 3, 4]

Costs

OperationCostNotes
+, -, *, /, %, comparisons on numberO(1)one machine operation on a double or a small integer
bigint arithmeticO(digits), allocatesfor 10⁹-sized modular products, about 1.5× the time of mulmod (Node 24)
Math.floor(a / b)O(1)the exact quotient for safe integers
String(n), Number(s), parseInt(s, 10)O(length)
n.toString(2)O(bits)
Math.max(...xs)O(n), one argument per elementRangeError past the engine's argument limit
new Int32Array(n)O(n) to zero-fill4 bytes per element; a plain array of small integers took 8

DS&A idioms

The integer moves problem solutions use, and the trap in each. The patterns are in the DS&A sheets (linked in the last column).

JobTypeScriptWatch outMore
Sentinelslet best = Infinity, let worst = -Infinityfine with Math.min, Math.max and <; but Infinity | 0 is 0
Integer divisionMath.floor(a / b) or Math.trunc(a / b)a / b | 0 wraps past 2³¹TS gotchas
32-bit overflowif (r > 2 ** 31 - 1 || r < -(2 ** 31)) return 0;a number holds the overflowed value exactly, so test before returningRecipes
Midpointlo + Math.floor((hi - lo) / 2), or (lo + hi) >>> 1>>> 1 only while lo + hi < 2 ** 32; >> 1 breaks at 2³¹Binary search
Search on the answerlet lo = 0, hi = 1e9; then shrinkintegers: exact; floats: a fixed number of iterationsOn the answer
Parity(n & 1) === 1, or n % 2 !== 0n % 2 === 1 misses negative odd numbers: -7 % 2 is -1
Wraparound index((i % n) + n) % n% keeps the dividend's sign
Power of twon > 0 && (n & (n - 1)) === 0the parentheses matter: === binds tighter than &; only for n < 2 ** 31Bit manipulation
Bitmask subsetsfor (let m = 0; m < 1 << n; m++), test (m >> i) & 11 << 31 is negative: n up to 30Input size
Grid index ↔ cellr = Math.floor(i / cols), c = i % cols, i = r * cols + ccols, not rows, is the divisorGrids as graphs
Digits[...String(Math.abs(n))].map(Number), or % 10 with Math.floor(m / 10)take Math.abs first: -123 % 10 is -3Fast & slow pointers
Answers mod pconst MOD = 1_000_000_007, reduce after every stepproducts need mulmod or BigIntModular arithmetic
Fast powermodPow(b, e, MOD)halve e with Math.floor(e / 2) when it may pass 2³¹
Divisor loopsfor (let d = 1; d * d <= n; d++)
Perfect squareisqrt(n) ** 2 === nNumber.isInteger(Math.sqrt(n)) is safe only below 2⁵²Math & number theory
Missing numbern * (n + 1) / 2 - sum, or xor everythingexact while the sum stays below 2⁵³Index as hash
Float equalityMath.abs(a - b) < 1e-9Number.EPSILON is far too small away from 1
Floor log₂31 - Math.clz32(n) (32-bit), n.toString(2).length - 1Math.floor(Math.log2(2 ** 50 - 1)) is 50, one too high
Sorting numbersxs.sort((a, b) => a - b)the default sort compares stringsThe default sort trap
Counting arraysnew Int32Array(26), index ch.charCodeAt(0) - 97already zero-filledTyped arrays
Max of manya loop, or xs.reduce((a, b) => Math.max(a, b))Math.max(...xs) throws past about 125 000 elements on Node
const cols = 4;
const i = 7;
[Math.floor(i / cols), i % cols]; // [1, 3]
 
const lo = 2 ** 30;
const hi = 2 ** 30 + 10;
(lo + hi) >> 1; // -1073741819: lo + hi passed 2 ** 31
(lo + hi) >>> 1; // 1073741829
lo + Math.floor((hi - lo) / 2); // 1073741829
 
const isPow2 = (n: number): boolean =>
  n > 0 && (n & (n - 1)) === 0;
[isPow2(64), isPow2(96), isPow2(0)]; // [true, false, false]
-7 % 2 === 1; // false: -7 is odd, but % gives -1
(-7 & 1) === 1; // true

Typing

AnnotationFor
number, bigintthe two numeric primitives (never Number or BigInt)
0 | 1, -1 | 0 | 1literal unions: bits, signs, comparator results
readonly [number, number]a pair: a grid cell, [value, index], an edge
as constconst DIRS = [[0, 1], [1, 0]] as const keeps the literal tuples
number & { readonly __brand: "NodeId" }a branded id, so a node id can't be passed where a weight belongs
a const object instead of enumNode 24 and 26 run .ts files by stripping types, and reject enum (ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX); TS 5.8+ flags it under erasableSyntaxOnly
noUncheckedIndexedAccessxs[i], and counts[i] on a typed array, become number | undefined: check, use ?? 0, or xs[i]! once the bounds are proved

TypeScript catches bigint mixed with number (TS2365), Math.max(1n) (TS2345) and the n & 1 === 0 precedence bug (TS2363). It can't see integer versus fraction, safe versus unsafe, or 32-bit wrapping: all of those are number. Number.isInteger and Number.isSafeInteger are not type guards.

type NodeId = number & { readonly __brand: "NodeId" };
const nodeId = (n: number): NodeId => n as NodeId;
 
function dist(from: NodeId, to: NodeId): number {
  return Math.abs(from - to);
}
const a = nodeId(3);
dist(a, nodeId(7)); // 4
// @ts-expect-error: a plain number is not a NodeId
dist(a, 7);
 
const DIRS = [[0, 1], [1, 0], [0, -1], [-1, 0]] as const;
type Step = (typeof DIRS)[number]; // readonly [0, 1] | ...
const sign = (x: number): -1 | 0 | 1 =>
  x < 0 ? -1 : x > 0 ? 1 : 0;
[sign(-4), sign(0), sign(9)]; // [-1, 0, 1]
 
const Color = { Red: 0, Green: 1 } as const;
type Color = (typeof Color)[keyof typeof Color]; // 0 | 1
const c: Color = Color.Green; // 1

Coming from Python

PythonTypeScript
int (exact, unbounded)number (exact up to 2⁵³) or bigint (10n)
floatnumber: the same IEEE 754 double
a // bMath.floor(a / b)
int(a / b)Math.trunc(a / b)
a % b (divisor's sign: -7 % 3 == 2)((a % b) + b) % b; JS % is Python's math.fmod
divmod(a, b)[Math.floor(a / b), ((a % b) + b) % b]
round(2.5) == 2 (half to even)Math.round(2.5) === 3 (half toward +∞)
f"{x:.2f}"x.toFixed(2)
int("ff", 16); int("12px") raisesparseInt("ff", 16); parseInt("12px", 10) is 12, Number("12px") is NaN
bin(x), format(x, "b")x.toString(2)
x.bit_count()no built-in: popcount
x.bit_length()32 - Math.clz32(x) (32-bit), x.toString(2).length
1 << 31 == 21474836481 << 31 === -2147483648; use 2 ** 31
x & 0xFFFFFFFFx >>> 0
pow(b, e, m), pow(a, -1, m)modPow(b, e, m), and modPow(a, m - 2, m) for a prime m, from Modular arithmetic; or bigint
math.gcd, math.isqrt, math.combwrite them: Math & number theory, nCr mod p
math.isclose(a, b)Math.abs(a - b) <= tol
math.inf, math.nan, math.isnan(x)Infinity, NaN, Number.isNaN(x)
sys.maxsizeNumber.MAX_SAFE_INTEGER is the nearest idea: the exact-integer limit
sum(xs) (compensated since 3.12)xs.reduce((a, b) => a + b, 0): plain, rounds at each step
array.array("i"), bytearrayInt32Array, Uint8Array

Recipes

Sieve of Eratosthenes

When you need every prime up to n (counting primes, prime factors of many numbers).

function primesUpTo(n: number): number[] {
  if (n < 2) return [];
  const composite = new Uint8Array(n + 1); // all 0
  const primes: number[] = [];
  for (let p = 2; p <= n; p++) {
    if (composite[p]) continue;
    primes.push(p);
    for (let m = p * p; m <= n; m += p) composite[m] = 1;
  }
  return primes;
}
 
primesUpTo(30);
// [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
primesUpTo(1e6).length; // 78498

O(n log log n) time and one byte per number. A boolean[] works too, but a Uint8Array is allocated zero-filled in one step.

Prime factorization

When you need the prime factors of one number (divisor counts, gcd tricks).

function factorize(n: number): Map<number, number> {
  const f = new Map<number, number>();
  for (let d = 2; d * d <= n; d += d === 2 ? 1 : 2) {
    while (n % d === 0) {
      f.set(d, (f.get(d) ?? 0) + 1);
      n /= d;
    }
  }
  if (n > 1) f.set(n, (f.get(n) ?? 0) + 1); // a prime
  return f;
}
 
const f = factorize(360); // Map(3) {2 => 3, 3 => 2, 5 => 1}
let divisors = 1;
for (const e of f.values()) divisors *= e + 1;
divisors; // 24

O(√n). For many queries up to one limit, sieve the smallest prime factor of every number once instead.

nCr mod p

When a counting problem needs many binomial coefficients modulo a prime.

const MOD = 1_000_000_007;
const N = 100_000;
 
function mulmod(a: number, b: number, m: number): number {
  const hi = (a * (b >>> 16)) % m;
  return (hi * 65536 + a * (b & 65535)) % m;
}
 
function modPow(b: number, e: number, m: number): number {
  let r = 1;
  for (; e > 0; e = Math.floor(e / 2)) {
    if (e % 2 === 1) r = mulmod(r, b, m);
    b = mulmod(b, b, m);
  }
  return r;
}
 
const fact = new Array<number>(N + 1).fill(1);
for (let i = 1; i <= N; i++) {
  fact[i] = mulmod(fact[i - 1], i, MOD);
}
const invFact = new Array<number>(N + 1).fill(1);
invFact[N] = modPow(fact[N], MOD - 2, MOD); // Fermat
for (let i = N; i > 0; i--) {
  invFact[i - 1] = mulmod(invFact[i], i, MOD);
}
 
function nCr(n: number, r: number): number {
  if (r < 0 || r > n) return 0;
  const top = mulmod(fact[n], invFact[r], MOD);
  return mulmod(top, invFact[n - r], MOD);
}
 
[nCr(5, 2), nCr(100_000, 50_000)]; // [10, 149033233]

O(N) setup with a single modular inverse, then O(1) per query.

Reverse Integer with a 32-bit clamp

When a problem written for fixed-width integers wants 0 on overflow.

const INT_MAX = 2 ** 31 - 1;
const INT_MIN = -(2 ** 31);
 
function reverseInt(x: number): number {
  let n = Math.abs(x); // -123 % 10 would be -3
  let r = 0;
  while (n > 0) {
    r = r * 10 + (n % 10);
    n = Math.floor(n / 10);
  }
  if (x < 0) r = -r;
  return r < INT_MIN || r > INT_MAX ? 0 : r;
}
 
[reverseInt(-123), reverseInt(120)]; // [-321, 21]
reverseInt(1534236469); // 0: 9646324351 overflows

r can hold the overflowed value exactly (it stays far below 2⁵³), so the check after the loop is enough. In C or Java the multiply itself would wrap and the check would have to come first.

Add binary strings

When two binary (or hex) strings are too long for a number.

const addBinary = (a: string, b: string): string =>
  (BigInt(`0b${a}`) + BigInt(`0b${b}`)).toString(2);
 
addBinary("1011", "11"); // "1110"
addBinary("1".repeat(100), "1").length; // 101
(255n).toString(16); // "ff"
BigInt("0xff"); // 255n

BigInt parses 0b, 0o and 0x prefixes, and toString(radix) writes any base from 2 to 36. The digit-by-digit carry loop is the answer when the interviewer rules BigInt out.

Counting bits

When you need the popcount of every number from 0 to n, in O(n).

function countBits(n: number): number[] {
  const bits = new Array<number>(n + 1).fill(0);
  for (let i = 1; i <= n; i++) {
    bits[i] = bits[i >> 1] + (i & 1); // drop the last bit
  }
  return bits;
}
 
countBits(5); // [0, 1, 1, 2, 1, 2]

i >> 1 has every bit of i except the last one, and was computed earlier in the loop.

Fibonacci mod m by matrix power

When n is huge (10¹⁸) and the recurrence is linear: square the matrix instead of looping n times.

type Mat = readonly [number, number, number, number];
 
function mulmod(a: number, b: number, m: number): number {
  const hi = (a * (b >>> 16)) % m;
  return (hi * 65536 + a * (b & 65535)) % m;
}
 
function matMul(x: Mat, y: Mat, m: number): Mat {
  const [a, b, c, d] = x; // [[a, b], [c, d]]
  const [e, f, g, h] = y;
  const mm = (p: number, q: number, r: number, s: number) =>
    (mulmod(p, q, m) + mulmod(r, s, m)) % m;
  return [
    mm(a, e, b, g),
    mm(a, f, b, h),
    mm(c, e, d, g),
    mm(c, f, d, h),
  ];
}
 
function fib(n: number, m = 1_000_000_007): number {
  let result: Mat = [1, 0, 0, 1]; // identity
  let base: Mat = [1, 1, 1, 0];
  for (; n > 0; n = Math.floor(n / 2)) {
    if (n % 2 === 1) result = matMul(result, base, m);
    base = matMul(base, base, m);
  }
  return result[1]; // base ** n = [[F(n+1), F(n)], ...]
}
 
Array.from({ length: 10 }, (_, i) => fib(i));
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
fib(1e18); // 209783453

O(log n) matrix products. Any recurrence f(n) = c1·f(n−1) + … + ck·f(n−k) works with a k × k matrix.

Exact slopes: points on a line

When grouping points by direction; float slopes merge or split lines at large coordinates.

type Pt = readonly [number, number];
 
function gcd(a: number, b: number): number {
  while (b !== 0) [a, b] = [b, a % b];
  return Math.abs(a);
}
 
function direction(dx: number, dy: number): string {
  const g = gcd(dx, dy); // > 0 unless both are 0
  dx /= g;
  dy /= g;
  if (dx < 0 || (dx === 0 && dy < 0)) {
    dx = -dx; // one sign per direction
    dy = -dy;
  }
  return `${dx},${dy}`; // `${-0}` is "0"
}
 
function maxPoints(pts: readonly Pt[]): number {
  let best = Math.min(pts.length, 1);
  pts.forEach(([x1, y1], i) => {
    const dirs = new Map<string, number>();
    for (const [x2, y2] of pts.slice(i + 1)) {
      const key = direction(x2 - x1, y2 - y1);
      const c = (dirs.get(key) ?? 0) + 1;
      dirs.set(key, c);
      best = Math.max(best, c + 1);
    }
  });
  return best;
}
 
const pts: Pt[] = [
  [1, 1], [3, 2], [5, 3], [4, 1], [2, 3], [1, 4],
];
maxPoints(pts); // 4

O(n²) pairs. The key is a string because Map compares arrays by reference: [1, 2] and [1, 2] would be two different keys. Assumes distinct points.

References