Math.min()
ES1+Returns the smallest of zero or more numbers passed as separate arguments. With no arguments it returns Infinity, and any argument that coerces to NaN makes the result NaN. Like Math.max(), it takes separate arguments, so arrays are spread into it, with the same large-array limits.
Syntax
Math.min(value1, value2, ...)Parameters
values number Zero or more numbers
Return Value
The smallest of the given numbers, or Infinity if no arguments
Examples
console.log(Math.min(1, 3, 2));
console.log(Math.min(-1, -3, -2));
console.log(Math.min(...[4, 5, 6])); 📌 When to Use
Use Math.min() to take the smaller of a few candidate values: capping a computed value at an upper bound (Math.min(requested, available) never over-allocates), finding the cheapest price or earliest timestamp among a handful of options, limiting pagination size to what remains (Math.min(pageSize, total - offset)), or forming the upper half of the clamp idiom with Math.max(). It expresses "no more than" constraints declaratively, which is why it saturates UI math: progress can be Math.min(100, percent), a countdown floor-limited elsewhere, retry delays capped at a maximum backoff, and text truncated to Math.min(maxLength, text.length). The empty-call identity Infinity is the mirror of max's -Infinity and equally hazardous when spread over an empty array; guard collections before aggregating. NaN remains absorbing, and one subtlety distinguishes it from a naive comparison chain: Math.min(0, -0) is -0 (the spec orders -0 below +0 here), whereas -0 < 0 is false, so hand-rolled minimum loops using < treat the two zeros as interchangeable while Math.min does not, an edge that matters only to code distinguishing zeros via Object.is or 1/x. For large arrays, prefer reduce or a loop for the same stack-limit reasons as Math.max.
⚠️ Common Mistakes
Expecting 0 from an empty call: Math.min() and Math.min(...[]) return Infinity, the correct mathematical identity but a nonsense business value; "lowest price" widgets render the ∞ symbol in production more often than anyone admits. Check for empty inputs first.
NaN contamination: Math.min(5, NaN) is NaN, so a single unparsed "N/A" in a price list hides the true minimum. Filter with Number.isFinite before aggregation; unlike SQL MIN, nothing is skipped automatically.
Spreading unbounded arrays: Math.min(...prices) throws a RangeError once prices grows past the engine's argument limit (roughly 65k-120k). Data-dependent crashes like this pass code review because small fixtures work; use reduce for anything sizable.
Transposing the clamp: Math.min(min, Math.max(max, v)) pins everything to the minimum. Correct is Math.max(min, Math.min(max, v)); the reversed version is the single most common clamp bug and tests only catch it if they exercise both bounds.
Forgetting coercion: Math.min("25", "100") is 25 numerically, but the same strings compared as "25" < "100" is false lexicographically, so mixing Math.min with string comparisons of the same data produces contradictory orderings; convert once at the boundary.
Using min/max chains where sorting or a heap is the real need: taking the three smallest via repeated Math.min calls with splicing is O(n·k) and convoluted; partial sort or a single pass tracking k values expresses it better.
✅ Best Practices
Make bounds symmetrical and named: pair every Math.min cap with an explicit Math.max floor via a clamp(value, min, max) utility, and let call sites read as constraints, not arithmetic.
Aggregate arrays with reduce and an explicit identity: arr.reduce((a, b) => Math.min(a, b), Infinity), then translate the Infinity result for empty inputs into your domain's null case before it reaches the UI.
Clean data at the edges: a single values.filter(Number.isFinite) before min/max/avg protects the whole statistics block, and centralizing it means the rule is applied consistently.
Use Math.min for resource arithmetic: bytesToRead = Math.min(chunkSize, remaining), visibleRows = Math.min(rowsPerPage, total - start); saturating arithmetic like this eliminates whole classes of overflow and out-of-bounds bugs.
For streaming minima or sliding windows, keep an accumulator or a monotonic deque rather than re-spreading the window each tick; O(1) updates keep frame budgets intact in dashboards that update per event.
⚡ Performance Notes
Math.min() shares Math.max()'s profile exactly: constant-time and inline-cheap for a few arguments, O(n) with stack-materialization overhead and a hard argument-count ceiling when spread over arrays. For large datasets, a for-loop with a running minimum is fastest, reduce is close behind and more declarative, and both avoid the RangeError cliff entirely. When you need min and max together, compute them in one pass rather than two scans, one loop maintaining both accumulators halves memory traffic, which is the actual bottleneck on large numeric arrays. As with max, keep inputs type-stable (ideally Float64Array for bulk numerics) so the JIT stays on unboxed-double paths; and never spread in a hot path even for mid-sized arrays, because argument-object setup costs dwarf the comparisons themselves.
🌍 Real World Example
Progress Bar with Bounds
Progress indicators must never overshoot: uploads report more bytes than expected, streaming totals arrive late, and rounding pushes percentages past 100. This example caps computed progress with Math.min(100, ...) and floors it with Math.max(0, ...), the saturating-arithmetic pattern that keeps animations smooth and displays sane regardless of upstream noise. The same bounded-percentage logic drives download bars, quota meters, and completion rings, and the guard-empty-then-clamp structure generalizes to any metric computed from unreliable counters.
function clampProgress(current, total, options = {}) {
const { minPercent = 0, maxPercent = 100 } = options;
const rawPercent = (current / total) * 100;
// Clamp between min and max
const clampedPercent = Math.max(
minPercent,
Math.min(maxPercent, rawPercent)
);
return {
percent: clampedPercent,
display: clampedPercent.toFixed(1) + '%',
isComplete: clampedPercent >= maxPercent
};
}
console.log(clampProgress(75, 100)); // { percent: 75, display: '75.0%', isComplete: false }
console.log(clampProgress(150, 100)); // { percent: 100, display: '100.0%', isComplete: true }