In Ruby, what do row[-1], row[1, 2] and row[1..2] return, and what happens when an index is out of range?
answer
- negative counts back from the end
- second integer is a length
- two dots include the end
- start equal to size gives []
- start past size gives nil
basics
~20 srow[-1] is the last element; row[1, 2] and row[1..2] both return a new two-element array starting at index 1. An out-of-range index returns nil, and so does a slice whose start lies beyond the array's size.
solid answer
~40 s`Array#[]` with one integer returns one element, counting back from the end when the index is negative, so `row[-1]` is the last seat. An out-of-range index returns `nil` rather than raising; `Array#fetch` is the variant that raises `IndexError`. With two integers the second is a **length**: `row[1, 2]` returns a new array of up to two elements from index 1, truncated at the end. With a range, `1..2` includes the end and `1...3` excludes it, and a negative range end counts from the back, so `row[1..-2]` drops the first and last seats. The edges: a start equal to `row.size` returns `[]`, a start beyond it returns `nil`, and a negative length returns `nil`.
code
ruby · 15 linesrow = %w[A1 A2 A3 A4 A5]
row[-1] # => "A5"
row[99] # => nil
row[1, 2] # => ["A2", "A3"]
row[1..2] # => ["A2", "A3"]
row[1...3] # => ["A2", "A3"]
row[1..-2] # => ["A2", "A3", "A4"]
row[3, 10] # => ["A4", "A5"]
row[5, 1] # => [] start == size
row[6, 1] # => nil start > size
row[1, -1] # => nil negative length
row.fetch(9) # IndexError: index 9 outside of array bounds: -5...5go deeper
Recall that a second integer is a length, that two dots include the end and three exclude it, and that a negative index counts back from the last element.
Explain the edge where a start equal to the size gives an empty array but one step further gives nil, and when to reach for Array#fetch instead.
Show how nil from an out-of-range read hides bugs when nil is also a valid element, and choose fetch or explicit bounds checks where silence would corrupt data.
Weigh lenient nil-returning reads against strict fetch in shared code, and set a convention so callers are not guessing which one a helper uses.
## One index: an element or nil Ruby's `Array#[]` (also available as `Array#slice`) answers several different questions depending on its arguments. With a **single integer** it returns one element: - A **non-negative index** counts from the front: `row[0]` is the first element. - A **negative index** counts from the back: `row[-1]` is the last element, `row[-2]` the one before it. - An **out-of-range index**, positive or negative, returns `nil`. No exception is raised. That last point matters because `nil` is also a legitimate element. In a theatre seating chart where an empty seat is stored as `nil`, `row[7]` returning `nil` cannot tell you whether seat 8 is empty or does not exist. When a missing index is a bug, use `Array#fetch`: it raises `IndexError` (for example `index 9 outside of array bounds: -5...5`), or returns a default you pass as a second argument or a block's result. ## Two integers: start and length With **two integers**, `row[start, length]`, the second argument is a count, not an end index. The result is always a **new array**: - `row[1, 2]` returns two elements beginning at index 1. - If `start + length` runs past the end, the slice is truncated: `row[3, 10]` returns whatever is left from index 3. - A negative `start` counts from the back: `row[-2, 2]` returns the last two elements. - A negative `length` returns `nil`. ## Range slices A **range** gives start and end positions instead of a count. | Expression (row has 5 elements) | Meaning | Result | |---|---|---| | `row[1..2]` | indexes 1 through 2 | two elements | | `row[1...3]` | indexes 1 up to, not including, 3 | two elements | | `row[1..-2]` | index 1 through the second-to-last | three elements | | `row[2..]` | index 2 to the end (endless range) | three elements | | `row[-2..1]` | start after end | `[]` | Two dots **include** the end, three dots **exclude** it. A negative range end is resolved against the array's size before slicing, which is why `row[1..-2]` is the idiom for "everything but the first and last". Arrays can also be sliced with an arithmetic sequence: `row[(0..).step(2)]` returns every other element. Unlike a range slice, a sequence that runs past the array raises `RangeError` instead of being truncated. ## The edge cases interviewers ask about 1. **Start equal to the size.** `row[5, 1]` on a five-element array returns `[]`. Position 5 is where the next element would go, so a slice there is valid and empty. `row[5..]` returns `[]` for the same reason. 2. **Start beyond the size.** `row[6, 1]` and `row[6..]` return `nil`. 3. **Negative start beyond the front.** `row[-6, 1]` on a five-element array returns `nil`. 4. **Negative length.** `row[1, -1]` returns `nil`. The asymmetry between cases 1 and 2 is the classic trap: code that tests `if slice.empty?` crashes with `NoMethodError` when the slice is `nil`. ## Slices are new arrays A slice is a new `Array` object, so appending to it leaves the original array's length alone. The elements themselves are the **same objects**, not copies. To change the original through a slice position, assign to the slice: - `row[1, 2] = ["X"]` replaces two elements with one, shrinking the array. - `row[1, 0] = ["X", "Y"]` inserts without removing anything. - `row[7] = "A8"` on a five-element array pads indexes 5 and 6 with `nil`. ## Everyday slicing idioms - **Last n elements:** `row[-3..]` or `row.last(3)`. - **Drop the header row of a chart:** `chart[1..]`. - **A window of seats:** `row[start, width]`, which is truncated at the end of the row rather than padded. - **Every other seat:** `row[(0..).step(2)]`. Prefer the start-and-length form when the width is what you know, and a range when both ends are known. Mixing them up is a common slicing bug in review: `row[2, 4]` is four elements from index 2, not indexes 2 through 4. ## Literals you will see in these examples `%w[A1 A2 A3]` builds an array of strings split on whitespace, with no commas or quotes and no interpolation (`%W` interpolates). `%i[a b]` builds an array of symbols. A seat label containing a space needs a backslash: `%w[Box\ 1 A2]` gives `["Box 1", "A2"]`.
- Why does row[row.size, 1] return [] while row[row.size + 1, 1] returns nil?Index `row.size` is the position just past the last element, where the next element would be appended, so a slice starting there is valid and empty. One step further is outside the array altogether, and `Array#[]` signals that with `nil`. Code that calls `.empty?` or `.each` on a slice must handle both.
- How do you make an out-of-range read fail loudly instead of returning nil?Use `Array#fetch`. `row.fetch(9)` on a five-element array raises `IndexError` with `index 9 outside of array bounds: -5...5`. `row.fetch(9, :none)` returns the default, and `row.fetch(9) { |i| ... }` returns the block's value. Negative indexes count from the end, as with `[]`.
- What does %w[A1 A2 A3] build, and when does it trip you up?An array of three strings, split on whitespace with no quotes or commas. `%w` does not interpolate, so `%w[#{seat}]` gives the literal text; use `%W` for interpolation. A label containing a space must escape it: `%w[Box\ 1]`. `%i[...]` is the same literal for symbols.
saying these in an interview costs you the question
- Reading row[99] on a short array raises an IndexError
- row[2, 3] returns the elements from index 2 through index 3
- 1...3 includes index 3 because three dots make a longer range
- A slice starting exactly at row.size returns nil
- Negative indexes are invalid in Ruby arrays and raise an error
- Appending to a slice also appends to the original array