skip to content

In Ruby, how does String#split treat runs of whitespace, a single-space separator and trailing empty fields?

level: middleimportance: should knowfreq 40%

answer

  1. nil and " " are special
  2. awk-style: leading whitespace ignored
  3. trailing empties dropped by default
  4. limit -1 keeps them
  5. positive limit caps the parts

basics

~20 s

split with no argument or with " " splits on runs of whitespace and ignores leading whitespace. Any other separator splits on each occurrence, keeping empty fields between separators but dropping trailing ones unless a negative limit is passed.

solid answer

~40 s

`String#split` has two modes. With no argument (the separator `$;` defaults to `nil`) or with a **single space** `" "`, it splits on **runs of whitespace** and ignores leading and trailing whitespace: `" a b\t c ".split` is `["a", "b", "c"]`. Any other string splits on **each occurrence**, so consecutive separators produce empty strings: `"a,,b".split(",")` is `["a", "", "b"]`. By default **trailing** empty strings are removed, which silently shortens a CSV-like row whose last fields are blank; `split(",", -1)` keeps them. A positive limit caps the array size: `split("=", 2)` splits only at the first `=`, keeping the rest intact. With an empty string, `split("")` returns each character.

code

ruby · 6 lines
ruby
" Flat  white\t oat ".split   # => ["Flat", "white", "oat"]
" Flat  white".split(" ")    # => ["Flat", "white"]
" Flat  white".split(/ /)    # => ["", "Flat", "", "white"]
"Latte,2,,".split(",")        # => ["Latte", "2"]
"Latte,2,,".split(",", -1)    # => ["Latte", "2", "", ""]
"size=large=hot".split("=", 2) # => ["size", "large=hot"]

go deeper

for a junior

Recall that split with no argument splits on whitespace and that split(",") splits on commas. Predict the result for simple rows.

for a middle

Explain awk-style splitting, why split(" ") differs from split(/ /), and how limit 0, positive and negative treat trailing empty fields.

for a senior

Catch data-loss bugs in imports: rows shortened by dropped trailing fields, and values cut apart where a positive limit or a real parser was needed.

for a principal

Decide when ad-hoc splitting is acceptable in a data pipeline and when a format needs a dedicated parser and schema validation.

## Two modes of splitting `String#split(field_sep = $;, limit = 0)` returns an array of substrings. How it cuts depends on the separator: | Separator | Mode | `" a b ".split(sep)` | |---|---|---| | none (`$;` is `nil` by default) | runs of whitespace, awk-style | `["a", "b"]` | | `" "` (one space) | runs of whitespace, awk-style | `["a", "b"]` | | `","` or any other string | each occurrence | depends on the text | | `""` | every character | `[" ", "a", " ", " ", "b", " "]` | | a Regexp | each match | depends on the pattern | **Awk-style** splitting, named after the Unix tool, treats any mix of spaces, tabs and newlines as one separator and ignores whitespace at the ends. That makes it the right default for words typed by people. The single-space argument is special-cased: `split(" ")` does **not** split on each single space. To split on every space, use a Regexp such as `split(/ /)`. ## Empty fields With a non-whitespace separator, each occurrence is a cut: - `"a,,b".split(",")` is `["a", "", "b"]`: the empty field between two commas is kept. - `",a".split(",")` is `["", "a"]`: a **leading** empty field is kept. - `"a,b,,".split(",")` is `["a", "b"]`: **trailing** empty fields are removed. That last rule is the one that bites. A coffee-shop order export row such as `"Latte,2,,"` (notes and discount blank) comes back with two fields instead of four, and indexing `fields[3]` returns `nil`. ## The limit argument The second argument controls both size and trailing empties: 1. **Zero or omitted:** no size limit; trailing empty strings removed. 2. **Positive `n`:** at most `n` elements, so at most `n - 1` cuts; the last element holds the rest of the string, and trailing empty strings are kept. 3. **Negative:** no size limit; trailing empty strings **kept**. ```ruby "Latte,2,,".split(",") # => ["Latte", "2"] "Latte,2,,".split(",", -1) # => ["Latte", "2", "", ""] "note=extra=hot".split("=", 2) # => ["note", "extra=hot"] ``` The positive limit is the idiom for key-value lines where the value may contain the separator. ## A block form Given a block, `split` yields each substring and returns the receiver instead of an array, which avoids building the intermediate array for large inputs: ```ruby "Latte Mocha Cortado".split(" ") { |drink| puts drink } ``` ## Splitting with a Regexp A Regexp separator cuts at each match, which handles messy delimiters in one step: - `"Latte , Mocha,Cortado".split(/\s*,\s*/)` is `["Latte", "Mocha", "Cortado"]`: spaces around each comma are absorbed into the separator. - If the Regexp contains **capture groups**, the captured text is included in the result: `"1:2".split(/(:)/)` is `["1", ":", "2"]`. - The trailing-empty and limit rules are the same as for a string separator. A Regexp that matches a single space, `/ /`, does **not** get awk-style treatment, which is the usual reason two similar-looking calls return different arrays. ## Related tools `split` is not the only way to cut a string, and the alternatives are often clearer: - `partition(sep)` cuts at the **first** occurrence and returns three parts: `"size=large=hot".partition("=")` is `["size", "=", "large=hot"]`. It never raises and always returns three elements, even when the separator is missing. - `lines` and `each_line` split after each line separator and **keep** it, which pairs with `chomp`. - `chars` returns every character, the same result as `split("")`. ## Practical rules - For words typed by a person, use `split` with no argument. - For delimited data, always pass the delimiter **and** `-1`, or use a real parser; a CSV field can contain quoted commas that `split` cannot handle. - For key-value pairs, pass a positive limit so values keep their separators. - Never rely on the global `$;`; changing it affects every argument-less `split` in the process.

  • Why does " a b".split(/ /) return a leading empty string when " a b".split(" ") does not?
    Only a `nil` or single-space string separator triggers awk-style splitting, which skips leading whitespace and merges runs. A Regexp that matches one space cuts at every space, so the text before the first space becomes an empty first field.
  • What does "a,b".split(",", 1) return?
    `["a,b"]`. A limit of 1 allows at most one element, so no cut is made and the whole string is returned in a one-element array.
  • Why is split(",") not enough to parse CSV exports?
    CSV fields may be quoted and contain commas or newlines, which `split` cuts through. It also drops trailing empty fields unless given `-1`. A CSV parser handles quoting and keeps every field.

saying these in an interview costs you the question

  • split(" ") splits on every single space
  • split with a comma keeps trailing empty fields by default
  • A negative limit removes empty fields
  • split with no argument splits on commas
  • A positive limit n makes n cuts