skip to content

In Ruby, how do Integer#times, upto and downto replace a counting loop, and what does each call return?

level: juniorimportance: should knowfreq 55%

answer

  1. counting methods on Integer
  2. times yields 0 to n-1
  3. upto and downto include both ends
  4. block form returns the receiver
  5. no block returns an Enumerator

basics

~10 s

Ruby counts with methods on Integer: n.times yields 0 to n-1, a.upto(b) and a.downto(b) yield every integer between the two, inclusive. With a block each returns its receiver; without one each returns an Enumerator.

solid answer

~40 s

Instead of a `while` loop with a counter, Ruby code calls a method on an `Integer`. `5.times { |i| ... }` yields 0 through 4; `3.upto(6)` yields 3, 4, 5, 6; `6.downto(3)` yields 6, 5, 4, 3. Both limits of `upto`/`downto` are inclusive, and if the limit is already past the receiver (`3.upto(1)`, or a zero or negative `times`) the block never runs. With a block, each method returns its **receiver**, not the block values: `squares = 4.times { |i| i * i }` assigns `4`. Without a block each returns an `Enumerator`, so `4.times.map { |i| i * i }` gives `[0, 1, 4, 9]`. RuboCop's `Style/EachForSimpleLoop` even prefers `5.times { }` over `(1..5).each { }` when the block ignores its parameter.

code

ruby · 5 lines
ruby
p 4.times { |i| i * i }         # => 4
p 4.times.map { |i| i * i }     # => [0, 1, 4, 9]
p 3.upto(5).to_a                # => [3, 4, 5]
p 5.downto(3).to_a              # => [5, 4, 3]
p 3.upto(1) { |i| puts i }      # prints nothing, => 3

go deeper

for a junior

Recall the yielded values: times from 0 to n-1, upto and downto inclusive at both ends, and nothing at all when the limit is already past the receiver.

for a middle

Explain the return values: the receiver with a block, an Enumerator without one, and how that enables n.times.map.

for a senior

Choose between times, upto, step and a condition loop by what the code means, and write bounded retries with a fixed count.

for a principal

Encourage one counting idiom per codebase so reviewers never have to check a hand-written counter for off-by-one errors.

## Counting without a counter variable Languages with a C-style `for (i = 0; i < n; i++)` loop make you manage the counter yourself. Ruby has no such loop. The counting loop is a **method on `Integer`** that yields each number to a block: ```ruby 3.times { |attempt| puts "attempt #{attempt}" } # 0, 1, 2 1.upto(3) { |zone| puts "zone #{zone}" } # 1, 2, 3 3.downto(1) { |left| puts "#{left} left" } # 3, 2, 1 ``` There is no index to initialise, compare or increment, and no off-by-one in the loop header. ## What each method yields | Call | Values yielded | Runs when the range is empty? | |---|---|---| | `n.times` | `0, 1, ..., n - 1` | no: `0.times` and `-3.times` yield nothing | | `a.upto(b)` | `a, a + 1, ..., b` (inclusive) | no: `5.upto(4)` yields nothing | | `a.downto(b)` | `a, a - 1, ..., b` (inclusive) | no: `4.downto(5)` yields nothing | Points that interviewers probe: - `times` starts at **zero** and stops **before** `n`. It yields exactly `n` values. - `upto` and `downto` include **both** ends. `1.upto(3)` yields three values, not two. - Neither `upto` nor `downto` reverses direction for you. `3.upto(1)` does not count down; it yields nothing and returns `3`. ## What each call returns With a block, all three return their **receiver**: ```ruby 4.times { |i| i * i } # => 4 5.upto(10) { |i| i } # => 5 10.downto(8) { |i| i } # => 10 ``` A common mistake is to expect the block's values back: ```ruby squares = 4.times { |i| i * i } squares # => 4, not [0, 1, 4, 9] ``` The block's return values are discarded. When you want them, ask for an enumerator. ## Without a block: an `Enumerator` Called without a block, each method returns an `Enumerator` that knows its size, so any Enumerable method can follow: ```ruby 4.times.map { |i| i * i } # => [0, 1, 4, 9] 1.upto(3).to_a # => [1, 2, 3] 5.times.select(&:even?) # => [0, 2, 4] ``` This is how idiomatic Ruby builds a small array from a count: `n.times.map { ... }` rather than a counter, an empty array and `<<`. ## Choosing the right construct 1. **A fixed number of repetitions** that do not need the index: `n.times`. RuboCop's `Style/EachForSimpleLoop` flags `(0...n).each { }` and `(1..n).each { }` when the block takes no parameter and suggests `n.times { }`. 2. **A counted range with known ends**: `a.upto(b)` or `a.downto(b)`. 3. **A stride other than one** (every 5 minutes, every 0.5 degrees): `Numeric#step`. 4. **An unknown number of passes that ends on a condition**: `while`, `until` or `loop`. A bounded retry is a good example of the first case. A greenhouse controller that tries a flaky sensor three times: ```ruby reading = nil 3.times do |attempt| reading = sensor.read break if reading sleep(0.2 * (attempt + 1)) end ``` The count is fixed, the attempt number is available for back-off, and the result variable is declared outside the block so it survives. ## Why not a `while` counter? A `while` loop can count too: ```ruby i = 0 while i < 3 poll(i) i += 1 end ``` It works, but it has three moving parts that `3.times { |i| poll(i) }` removes: the initial value, the comparison, and the increment. Forgetting the increment is an infinite loop; writing `<=` instead of `<` is an extra pass. The counter also stays in scope after the loop, whereas the block parameter of `times` disappears with the block. In CRuby 4.0, `Integer#times` is itself written in Ruby as a small `while` loop in `numeric.rb`, so choosing the method costs readability nothing and moves the counter into code that is already tested. ## Common wrong answers - Saying `times` yields `1` to `n`. It starts at zero. - Saying `upto` excludes its limit. It includes it. - Assigning the result of a block-form call and expecting an array. The call returns the receiver. - Expecting `upto` to count downward when the limit is smaller. It yields nothing.

  • In Ruby, how do you get an array of the first five square numbers from Integer#times without an explicit accumulator?
    Call `times` without a block to get an `Enumerator`, then map it: `5.times.map { |i| i * i }` returns `[0, 1, 4, 9, 16]`. Use `(1..5).map { |i| i * i }` if you want to start at one.
  • What does 0.times or -2.times do in Ruby?
    Nothing is yielded. `times` counts from 0 while the counter is below the receiver, so any receiver of zero or less runs the block zero times and the call returns the receiver itself.

saying these in an interview costs you the question

  • Integer#times yields 1 through n
  • upto excludes its limit, like an exclusive range
  • 4.times with a block returns an array of the block values
  • 3.upto(1) counts down automatically
  • times needs a block, otherwise it raises an error