skip to content

In Ruby, what does each link of `raw.strip.squeeze(" ").split(" ").first(8).join(" ")` receive, and is `raw` changed afterwards?

level: juniorimportance: should knowfreq 50%

answer

  1. each return value is the next receiver
  2. a new object per link
  3. String, Array, then String again
  4. the original stays untouched
  5. nil anywhere breaks the chain

basics

~20 s

Each call's return value becomes the receiver of the next call: strip and squeeze return new Strings, split turns one into an Array, first(8) returns a new Array and join builds a String. None of these methods mutates, so raw is unchanged.

solid answer

~40 s

A Ruby method chain works because every method returns an object, and the next `.name` is sent to that object. Here `strip` returns a new String without surrounding whitespace, `squeeze(" ")` a new String with runs of spaces collapsed, `split(" ")` an **Array** of words, `first(8)` a new Array of at most eight words, and `join(" ")` a new String. Because these are non-bang methods that return fresh objects, `raw` still holds the original text, which is what makes such chains safe to reorder, extract or reuse. The type can change along the way, so each link must be a method of whatever the previous link returned. The price is one intermediate object per link, usually irrelevant for a title but visible in hot loops.

code

ruby · 8 lines
ruby
raw = "  Red   ceramic  mug, 350 ml  "

short = raw.strip.squeeze(" ").split(" ").first(3).join(" ")
short  # => "Red ceramic mug,"
raw    # => "  Red   ceramic  mug, 350 ml  "  unchanged

raw.strip.squeeze(" ").class               # => String
raw.strip.squeeze(" ").split(" ").class    # => Array

go deeper

for a junior

Recall that each call's return value is what the next call runs on, and that non-bang methods like strip return new objects, leaving the original alone.

for a middle

Trace the type at every link, including where it turns into an Array and back, and name the links that can return nil on edge cases.

for a senior

Judge when a long chain should be split into named steps for readability or debugging, and when intermediate allocations justify profiling a hot path.

for a principal

Set expectations for data-cleaning pipelines across a codebase: non-mutating chains by default, measured exceptions where allocation matters.

## How a chain is evaluated A **method chain** is a series of calls where each call is made on the result of the previous one: `a.b.c` means "call `b` on `a`, then call `c` on whatever `b` returned". Ruby evaluates it strictly left to right. There is no special chain syntax; it falls out of two facts: - Every Ruby method returns an object, even if only `nil`. - A `.name` after an expression sends `name` to the object that expression produced. So the question for each link is always the same: **what did the previous link return, and does that object respond to this method?** ## Walking the product-title pipeline Take a messy product title, `raw = " Red ceramic mug, 350 ml "`, and the chain `raw.strip.squeeze(" ").split(" ").first(8).join(" ")`: | Link | Receiver | Returns | |---|---|---| | `strip` | the original String | a new String without leading and trailing whitespace | | `squeeze(" ")` | that new String | a new String with each run of spaces reduced to one | | `split(" ")` | the squeezed String | an Array of words | | `first(8)` | the Array | a new Array of at most 8 elements | | `join(" ")` | the short Array | a new String | 1. The chain starts on a String and ends on a String. 2. In the middle it passes through **Arrays**, so the methods available change: `first(8)` is an Array method, and calling `squeeze` after `split` would raise `NoMethodError`. 3. Each link creates a **new object**; nothing is modified in place. ## Why `raw` is unchanged `strip`, `squeeze`, `split`, `first` and `join` are **non-mutating**: they read their receiver and build a result. After the chain, `raw` still contains the original padding and double spaces. That matters for three reasons: - **Safety.** The same `raw` can feed another pipeline, a log line or an audit record without surprise. - **Refactoring.** Links can be reordered, extracted into a helper, or cut short for debugging, and each prefix of the chain is a valid expression with a meaningful value. - **Predictable values.** Every non-bang link above always returns an object of the documented type, never `nil` "because nothing changed". Many methods have a **bang** twin, such as `strip!` or `squeeze!`, that edits the receiver instead. Those are designed for statements, not chains, because several of them return `nil` when there was nothing to change. ## What a chain costs Each link allocates its result: here three Strings and two Arrays, plus the word Strings that `split` creates. For a single title that is negligible. In a loop over millions of rows, the intermediates become garbage the collector must reclaim, and that is the point where a hot path is profiled and rewritten. Readability is the default reason to chain; allocation is the reason to measure before assuming. ## Where chains break - A link that can return **`nil`** turns the next call into `NoMethodError`. Methods such as `Array#first` with no argument on an empty Array, `Hash#[]` on a missing key or `Array#find` without a match return `nil` rather than raising. - A link that returns a **different type** than you assumed, for example an Array where you expected a String, fails at the next link or, worse, succeeds with a method of the same name that means something else. - A link that mutates, such as a bang method, changes objects other code may share. Knowing each link's return value, including its value on the edge cases, is the whole skill. ## How to answer in an interview Walk the chain aloud, one link at a time, naming the **receiver** and the **return type** of each call, exactly as in the table above. Then state that none of these links mutates, so `raw` is unchanged, and contrast that with the bang twins that edit in place and may return `nil`. Finish with one edge case, for example that an empty or all-whitespace title makes `split` return an empty Array and `join` return an empty String rather than failing. That answer shows you read a chain as a sequence of return values, which is what the question is testing.

  • What happens if you append `.capitalize` directly after `.split(" ")` in that chain?
    `split` returns an Array, and Array has no `capitalize` method, so Ruby raises `NoMethodError` for `capitalize` on the Array. Each link must be a method of the previous link's return value, so capitalizing words means capitalizing each element, or capitalizing the String before splitting or after joining.
  • When would you stop chaining and assign an intermediate result to a local variable?
    When an intermediate value needs a name to be understood, is used twice, or needs checking, such as an empty Array before `first`. A named local also makes a failing link easier to find, because the backtrace line then points at a single call instead of a long expression.

saying these in an interview costs you the question

  • Chaining strip and squeeze modifies raw in place.
  • Every link in a chain operates on the original object.
  • A chain keeps the same type from the first link to the last.
  • Non-bang methods return nil when there is nothing to change.
  • Ruby needs a special fluent API for calls to be chainable.