Enumerable#take_while
The take_while method returns elements from the beginning of a collection as long as the condition evaluates to true. The moment the condition returns false, iteration stops — no further elements are checked.
How it works
collection.take_while { |element| condition }
take_while passes each element to the block in order. While the block returns true, elements are collected. The first false result halts iteration and take_while returns what it has collected so far. Elements after the failure point are never evaluated.
Basic Usage
Take numbers while they are less than 10:
[1, 4, 7, 9, 12, 18].take_while { |n| n < 10 }
# => [1, 4, 7, 9]
Works with any sequence, including ranges and lazy enumerators:
Each element passes through the block in collection order. As soon as the block returns false or nil, the method stops collecting and ignores everything that follows. This eager-stopping behavior distinguishes take_while from select, which scans the entire collection regardless of condition.
(1..20).take_while { |n| n < 5 }
# => [1, 2, 3, 4]
[1, 2, 3, 4, 5].lazy.take_while { |n| n < 3 }.to_a
# => [1, 2]
With strings:
Ranges work naturally with take_while because they yield values in order. When combined with a lazy enumerator, the evaluation stays deferred until to_a forces the result. This lets you express early-termination logic without materializing more of the collection than you need.
words = ["apple", "banana", "cherry", "apricot", "blueberry"]
words.take_while { |w| w.start_with?("b") }
# => ["banana"]
take_while vs take
take and take_while look similar but behave differently:
| Method | Selection criteria | Stops when |
|---|---|---|
take(n) | Positional — first n elements | Exactly n elements have been collected |
take_while { block } | Conditional — block returns true | Block returns false for the first time |
Processing strings with take_while follows the same pattern as numbers: the block tests each word and the method collects until the condition fails. Because strings in Ruby respond naturally to comparison and pattern-matching operations, any predicate that works on a single element can serve as the stopping rule.
# take(3) always returns 3 elements
[1, 2, 3, 4, 5].take(3)
# => [1, 2, 3]
# take_while stops on the first false
[1, 2, 3, 4, 5].take_while { |x| x < 3 }
# => [1, 2]
If take is given a number larger than the collection, it returns everything. If take_while never gets a false result, it also returns everything:
The contrast between take and take_while comes down to how the stopping condition is defined. With take(n), the count is fixed ahead of time and the result length is predictable before the call even runs. With take_while, the result length depends on the data itself, which makes it more flexible but also less predictable.
[1, 2].take(10)
# => [1, 2]
[1, 2].take_while { |x| x < 10 }
# => [1, 2]
Relationship to each
take_while is conceptually equivalent to manually breaking out of an each loop as soon as the condition fails:
When the condition never fails, take_while essentially acts like to_a. This behavior is consistent with the method’s contract: collect everything until you see a reason to stop. Since the reason never arrives, you get the full collection back.
# take_while
result = [1, 4, 7, 9, 12].take_while { |n| n < 10 }
# => [1, 4, 7, 9]
# Manual equivalent
result = []
[1, 4, 7, 9, 12].each do |n|
if n < 10
result << n
else
break
end
end
# => [1, 4, 7, 9]
The key difference is that take_while returns the collected elements directly, while the manual approach requires setting up an accumulator variable.
Practical Examples
Take until a sentinel value
Process a collection up to a terminating marker:
The manual equivalent shows what take_while saves you from writing: an accumulator variable, an explicit each loop, and a break statement. By packaging that logic into a single method call, the intent stays at the surface of the code instead of being buried inside the loop body.
data = ["header", "row1", "row2", "row3", "---", "more rows"]
data.take_while { |item| item != "---" }
# => ["header", "row1", "row2", "row3"]
This is useful for reading configuration lines until a divider, or processing log entries until a blank line.
Take first matching group
Extract the first consecutive group of elements sharing a property:
Using take_while with a sentinel value works like a text scanner that stops at a known boundary. The sentinel itself is excluded from the result, which is usually what you want when processing structured documents like CSV files or configuration blocks that use separator lines.
transactions = [100, -50, -30, 200, -10, 400, -20]
# Group of consecutive deposits (positive values)
transactions.take_while { |t| t > 0 }
# => [100]
# Reset: find the next group's start
remaining = transactions.drop_while { |t| t > 0 }
# => [-50, -30, 200, -10, 400, -20]
remaining.take_while { |t| t < 0 }
# => [-50, -30]
Skip header lines
Remove leading metadata from a document:
When the elements in a collection have a natural order that groups related items together, take_while pulls out the first contiguous block. Then drop_while on the remaining elements gives you the next block, letting you partition the data sequentially without building intermediate arrays for every group.
lines = ["# Config", "host: localhost", "---", "data: foo", "data: bar"]
lines.take_while { |line| !line.start_with?("---") }
# => ["# Config", "host: localhost"]
Filter sorted data
Since take_while stops at the first non-matching element, it is efficient on sorted collections:
Processing documents that begin with metadata lines is a common task in data ingestion pipelines. Rather than checking every line for header patterns in a loop, take_while cleanly separates the preamble from the data, producing a well-defined boundary between the two regions of the input.
scores = [95, 88, 76, 65, 55, 42]
# All scores above a threshold (stops immediately after first failure)
scores.take_while { |s| s >= 70 }
# => [95, 88, 76]
take_while vs drop_while
take_while and drop_while are complementary — one keeps the leading elements, the other discards them:
| Method | Return value |
|---|---|
take_while { block } | Leading elements where block is true |
drop_while { block } | Trailing elements starting from first false |
Because take_while stops at the first failure, it is especially efficient on sorted collections. After a single element fails the condition, no further elements are checked, so the runtime is proportional to the number of matching elements rather than the total collection size.
data = [1, 3, 5, 6, 7, 9]
data.take_while(&:odd?) # => [1, 3, 5]
data.drop_while(&:odd?) # => [6, 7, 9]
Together they partition a collection at the first failure point:
Together, take_while and drop_while partition a collection at the first point where the condition fails. The division is clean: one method gives you everything before the break, the other gives you everything from the break onward. No element appears in both results, and no element is lost in the split.
data = [2, 4, 6, 8, 10, 12]
threshold = 7
leading = data.take_while { |x| x < threshold }
trailing = data.drop_while { |x| x < threshold }
puts "Below #{threshold}: #{leading.inspect}" # => [2, 4, 6]
puts "#{threshold} and above: #{trailing.inspect}" # => [8, 10, 12]
See Also
- /reference/enumerable/enumerable-take/ — return the first n elements
- /reference/enumerable/enumerable-drop-while/ — skip elements while condition is true
- /reference/enumerable/enumerable-each-slice/ — group elements into fixed-size chunks