Generate a Numeric Range
JavaScript still has no built-in `range()` like Python, so every codebase eventually grows its own. This snippet shows the canonical `Array.from` trick for `[0, n)`, a `start, end, step` variant that handles negative steps, and a lazy generator for huge ranges where allocating the full array is wasteful. Use it for pagination, retries, table rows, and any test fixture that needs N of something.
1,186 views
14
function range(n) {
return Array.from({ length: n }, (_, i) => i);
}
console.log(range(5));
// [0, 1, 2, 3, 4]
console.log(range(0));
// []Array.from({ length: n }, mapFn) is the cleanest way to materialise a sized array in JavaScript. The first argument is an array-like with just a length property, and the optional mapFn is invoked for each index. The pattern is more readable than [...Array(n).keys()] once you see it a few times, and it handles n = 0 by returning [] without special casing. This is the version you want for the 90% case where you just need indices 0 through n - 1.
function rangeStep(start, end, step = 1) {
if (step === 0) throw new RangeError('rangeStep: step must be non-zero');
const length = Math.max(0, Math.ceil((end - start) / step));
return Array.from({ length }, (_, i) => start + i * step);
}
console.log(rangeStep(2, 8)); // [2, 3, 4, 5, 6, 7]
console.log(rangeStep(0, 10, 2)); // [0, 2, 4, 6, 8]
console.log(rangeStep(10, 0, -2)); // [10, 8, 6, 4, 2]The Python-style range(start, end, step) is what people usually mean when they reach for a range helper (here named rangeStep to avoid clashing with the simpler one above). Computing the length up front via Math.ceil((end - start) / step) lets Array.from allocate exactly once, which is faster than pushing in a loop. Negative steps work because the Math.max(0, ...) clamp prevents a negative length when start > end and step > 0. The step === 0 guard is the easy-to-forget trap that turns the whole thing into an infinite NaN.
function* rangeLazy(start, end, step = 1) {
if (step === 0) throw new RangeError('rangeLazy: step must be non-zero');
if (step > 0) {
for (let i = start; i < end; i += step) yield i;
} else {
for (let i = start; i > end; i += step) yield i;
}
}
let first5 = [];
for (const n of rangeLazy(0, 1000000)) {
first5.push(n);
if (first5.length === 5) break;
}
console.log(first5);
// [0, 1, 2, 3, 4]Allocating an array of one million integers just to read the first five is wasteful. A generator yields each value on demand and stops the moment the consumer stops asking, so memory stays constant. This pairs naturally with for..of, destructuring, spread into another consumer, or libraries that accept iterables. The split into two loops avoids subtle off-by-one issues when step is negative, where i < end would never be true and you'd silently get an empty range.
