← Math in Practice
Concept Quantities and representation

Modular arithmetic

Use remainders to wrap values through a cycle, while keeping the sign convention explicit.

A worker pool has seven slots. Its cursor is at slot 5 and the scheduler advances four positions. The next position should wrap back to slot 2. A remainder gives us a compact rule for that cycle, but negative movement exposes an important difference between remainder and modulo.

The judgment to keep

Use a positive modulus to describe the cycle size. Check the quotient and remainder, state whether values may be negative, and normalize the remainder when an index must stay in [0, capacity).

TypeScriptGo Remainder · wraparound · ring buffers · negative values
01 / Follow the ring

Make a cursor advance without falling off the end of a fixed-size buffer.

Imagine a seven-slot worker queue whose next-work cursor points to slot 5. After assigning one task, it advances four slots because several workers completed together. Counting forward gives 6, then 0, 1, then 2. The cursor must cycle through valid slots rather than become 9.

This pattern appears in circular buffers, rotating schedules, and bounded sequence numbers. The cycle has seven positions, numbered 0 through 6. The code should make two rules visible: capacity must be positive, and the starting index must already be in range.

Case file / Worker ring Advance the cursor four slots in a seven-slot cycle.
Capacity
7 slots, indexed 0 through 6.
Current index
5, already in the valid range.
Step
+4 slots; movement may later be backward too.
Question
Which slot is four moves ahead, with wraparound?
Leave with: capacity, valid index range, movement direction, and an explicit policy for negative steps.
02 / Read quotient and remainder

Division counts complete cycles and leaves a remainder.

For whole numbers a and positive m, division can be written as a = q × m + r. The quotient q counts cycles of size m; the remainder r is what remains after those cycles. For non-negative values, 0 ≤ r < m.

9 = 1 × 7 + 2

So 9 items contain one complete group of 7 and two items into the next group. If the group is a circular set of slots, slot 9 maps to slot 2. This equality is a useful audit: the quotient and remainder must reconstruct the original value.

Trace the cursor four steps from slot 5 in a seven-slot ring
Unwrapped positionComplete cyclesRemainder / slot
5 + 4 = 91 × 72
9 = 1 × 7 + 21 cycleslot 2
03 / Turn a remainder into a slot

Normalize into the valid interval when the index must be non-negative.

For a positive capacity m, a normalized modulo operation returns a value from 0 through m − 1. A common expression is ((x % m) + m) % m; because x % m is already less than m in magnitude, adding the positive modulus once is enough to lift a negative remainder into range. With JavaScript safe integers near their maximum, a conditional helper avoids an intermediate sum that may no longer be exactly representable.

For a step of −3 slots with capacity 7, language remainder gives −3. Normalization gives −3 + 7 = 4, so a position three slots backward from 0 lands at slot 4. Check that 0 ≤ result < 7 before treating it as an array index.

Boundary check: for capacity 7, both a step of 7 and a step of −7 normalize to 0; a step of −8 normalizes to 6.
04 / Diagnose negative values

TypeScript and Go agree on remainder sign, but that is not Euclidean modulo.

JavaScript's Number % operator and Go's integer % both use a quotient truncated toward zero. The nonzero remainder therefore has the dividend's sign. For example, −11 / 4 truncates to −2, so −11 = (−2 × 4) + (−3) and −11 % 4 = −3.

This differs from floor division: floor(−11 / 4) = −3 and the associated non-negative remainder would be 1. The language operators have not failed; they implement a different convention. The distinction matters for timestamps before an epoch, negative hash inputs, or a cursor that can move backward.

Remainder and normalized modulo with positive modulus 7
ValueLanguage remainderNormalized resultReconstruction
103310 = 1 × 7 + 3
−3−34−3 = 0 × 7 + (−3)
−11−43−11 = (−1 × 7) + (−4)
05 / Move around the ring

Change the capacity, position, or signed step and inspect the wrapped result.

Try a step larger than the capacity, then try a negative step. The lab shows how many positions the movement represents after complete cycles are removed. The start index must be a valid physical slot, but the step may be any safe integer.

Wraparound lab / Worker ring Advance one cursor in a fixed-capacity ring.

Exact start + step: 9

Normalized step: 4

Next physical slot: 2

Invariant: 0 ≤ 2 < 7

06 / Practice in code

Put the range and sign rules inside a named helper.

The implementations reject invalid capacities and out-of-range starting indices. TypeScript also requires safe integer inputs because its ordinary Number representation cannot exactly represent every integer. Both normalize the step and then wrap without creating a potentially large index + step intermediate.

Compare normalized modulo and cursor movement in TypeScript and Go.

Follow the signed remainder first, then verify the final index invariant.

TypeScriptNormalize a remainder and advance a ring-buffer index
modular.ts
/** Return the Euclidean remainder in [0, modulus) for safe integers. */
export function modulo(value: number, modulus: number): number {
	if (!Number.isSafeInteger(value) || !Number.isSafeInteger(modulus)) {
		throw new RangeError('Value and modulus must be safe integers.');
	}
	if (modulus <= 0) throw new RangeError('Modulus must be greater than zero.');
	const remainder = value % modulus;
	if (remainder === 0) return 0;
	return remainder < 0 ? remainder + modulus : remainder;
}

/** Move a ring-buffer index by any safe integer number of slots. */
export function advanceIndex(index: number, step: number, capacity: number): number {
	if (
		!Number.isSafeInteger(index) ||
		!Number.isSafeInteger(step) ||
		!Number.isSafeInteger(capacity)
	) {
		throw new RangeError('Index, step, and capacity must be safe integers.');
	}
	if (capacity <= 0) throw new RangeError('Capacity must be greater than zero.');
	if (index < 0 || index >= capacity) throw new RangeError('Index must be inside the ring.');

	const wrappedStep = modulo(step, capacity);
	const slotsToEnd = capacity - index;
	return wrappedStep >= slotsToEnd ? wrappedStep - slotsToEnd : index + wrappedStep;
}

// Example: advanceIndex(5, 4, 7) returns 2; modulo(-3, 7) returns 4.
GoNormalize a remainder and advance a ring-buffer index
modular.go
package modular

import "errors"

// Example: AdvanceIndex(5, 4, 7) returns 2; Modulo(-3, 7) returns 4.

// Modulo returns the Euclidean remainder in [0, modulus).
func Modulo(value, modulus int) (int, error) {
	if modulus <= 0 {
		return 0, errors.New("modulus must be greater than zero")
	}
	remainder := value % modulus
	if remainder < 0 {
		remainder += modulus
	}
	return remainder, nil
}

// AdvanceIndex moves a ring-buffer index by any integer number of slots.
func AdvanceIndex(index, step, capacity int) (int, error) {
	if capacity <= 0 {
		return 0, errors.New("capacity must be greater than zero")
	}
	if index < 0 || index >= capacity {
		return 0, errors.New("index must be inside the ring")
	}
	wrappedStep, err := Modulo(step, capacity)
	if err != nil {
		return 0, err
	}
	slotsToEnd := capacity - index
	if wrappedStep >= slotsToEnd {
		return wrappedStep - slotsToEnd, nil
	}
	return index + wrappedStep, nil
}
07 / Carry the idea forward

Use the cycle model, then inspect what the remainder does not guarantee.

Practice / Partitioned cache A service maps request keys across 12 cache partitions.
Observed hash
−29 for one key; another key hashes to 31.
Partition count
12, so valid partition IDs are 0 through 11.
Your task
Compute the language remainders, normalize each to a valid partition, then list two reasons the assignment may still be uneven.
Transfer question
What additional evidence would tell you whether this hash scheme balances the real key set?

For −29 and 12, truncating division gives quotient −2 and remainder −5; normalized modulo is 7, since −29 = (−3 × 12) + 7 under the Euclidean convention. For 31, the remainder is 7 under either convention. Both keys therefore land in partition 7 in this example. This is not proof that all keys collide there; two samples reveal almost nothing about distribution.

A good diagnosis would inspect the hash function, representative key samples, per-partition counts, and whether the partition count changes over time. Modulo answers “which bucket in this fixed set?” It does not answer “are the buckets balanced?” or “will existing keys move predictably if the set size changes?”

Transfer rule: state the divisor and remainder convention, check the output range, then gather the workload evidence needed for any distribution claim.

Language references: ECMAScript remainder operator and the Go integer operators specification.