In Ruby, what do Array#compact and Array#compact! each return, and what does compact! return when there is no nil to remove?
answer
- a returning and a bang pair
- compact: always a new array
- compact!: self or nil
- flatten! follows the same rule
- << and delete_at mutate without a bang
basics
~20 sArray#compact returns a new array without nil elements and leaves the receiver alone. compact! removes nils in place and returns self, but returns nil when nothing was removed. flatten and flatten! follow the same pattern.
solid answer
~40 s`compact` always returns a **new array** with the `nil` elements removed; `false`, empty strings and empty arrays stay. `compact!` edits the receiver and returns `self` if it removed anything, **`nil` if it removed nothing**. So `seats = seats.compact!` sets `seats` to `nil` on an already clean row, while `if row.compact!` reads as "there were nils". `flatten!` works the same way: it returns `nil` when nothing was flattened. The bang marks the in-place twin of a returning method, not every mutator: `<<` and `insert` mutate and return `self`, `delete_at` returns the removed element or `nil`, and `reverse!` always returns `self`. Read each method's documented return value rather than inferring it from the `!`.
code
ruby · 15 linesrow = ["A1", nil, "A3"]
row.compact # => ["A1", "A3"]
row # => ["A1", nil, "A3"]
row.compact! # => ["A1", "A3"]
row.compact! # => nil, nothing left to remove
row # => ["A1", "A3"]
chart = [["A1", "A2"], ["B1"]]
chart.flatten! # => ["A1", "A2", "B1"]
chart.flatten! # => nil
row << "A4" # => ["A1", "A3", "A4"]
row.insert(1, "A2") # => ["A1", "A2", "A3", "A4"]
row.delete_at(9) # => nil
row.delete_at(0) # => "A1"go deeper
Recall that compact returns a new array while compact! edits the receiver, and that only nil is removed.
Explain the self-or-nil contract of compact! and flatten!, and name mutators like <<, insert and delete_at that have no bang but still change the array.
Catch assignments from bang methods in review and choose the non-mutating form for arrays a method does not own.
Set expectations for how shared helpers treat arrays they receive, trading copy cost against surprise edits to caller data.
## The returning and the bang pair Many `Array` methods come in two forms: one that **returns a new array** and leaves the receiver alone, and a bang (`!`) twin that **modifies the receiver in place**. `compact` and `compact!` are the canonical pair. - **`compact`** returns a new array containing only the non-`nil` elements, in order. The receiver is unchanged. - **`compact!`** removes the `nil` elements from the receiver itself. It returns **`self` if any were removed** and **`nil` otherwise**. Only `nil` is removed. `false`, `0`, `""`, `[]` and `{}` are ordinary values and stay: `[nil, false, "A2"].compact` is `[false, "A2"]`. ## Why nil on no change Returning `nil` from `compact!` lets the caller ask "did anything change?" without comparing sizes: ```ruby puts "removed empty seats" if row.compact! ``` The cost is that the return value is not always the array. These patterns break on an array that is already clean: 1. `row = row.compact!` replaces `row` with `nil`. 2. `return row.compact!` returns `nil` to the caller. 3. Passing `row.compact!` as an argument passes `nil`. If you want the cleaned array, call `row.compact!` as a statement and then use `row`, or use `compact` and assign the new array. ## flatten and flatten! `flatten` and `flatten!` follow the same shape: | Call | Receiver changed? | Returns | |---|---|---| | `chart.flatten` | no | new array, all levels flattened | | `chart.flatten(1)` | no | new array, one level flattened | | `chart.flatten!` | yes, if nested | `self`, or `nil` if nothing was nested | | `chart.flatten!(0)` | no | `nil` | Flattening only unpacks arrays; hashes and sets inside stay as single elements. An array that contains itself raises `ArgumentError` (`tried to flatten recursive array`). ## Mutators without a bang The `!` marks the in-place twin of a method that also exists without it. It does **not** mark every method that mutates, and the return values vary: - **`<<`** appends one element and returns `self`, which is why `row << "A4" << "A5"` works. An array argument is appended as a single nested element. - **`insert(index, *objects)`** returns `self`. A non-negative index inserts before that position; a negative index inserts after it, so `insert(-1, x)` appends. A negative index beyond the front raises `IndexError`. - **`delete_at(index)`** returns the **removed element**, or `nil` when the index is out of range. It does not raise. - **`reverse!`** always returns `self`, even for a one-element array. Nil-on-no-change is a per-method contract, not a rule for every bang method. ## Why Ruby offers both The returning form is the safe default: it never surprises another holder of the same array, and it composes with further calls. The bang form exists for cost. `compact` allocates a new array for the survivors; `compact!` shuffles elements down inside the existing one. On a large array built and owned by the current method, such as a batch of seat records assembled from a file, the in-place form avoids a second large allocation. On an array passed in from elsewhere, that saving is rarely worth editing someone else's data. A useful review question for every bang call is: **who else holds a reference to this array?** If the answer is "a caller", "a constant" or "a cache", the returning form is the right one. ## Frozen arrays On a frozen array, `compact!` and `flatten!` raise `FrozenError` even when there would be nothing to change, because Ruby checks that the receiver may be modified before looking at its contents. `compact` and `flatten` work on frozen arrays because they build new ones. ## Choosing - In a method that receives someone else's array, prefer `compact`, so the caller's data is not edited behind its back. - Use `compact!` on an array you own and when avoiding a copy matters, and ignore or test its return value on purpose.
- What does insert do with a negative index?It inserts after the element at that position, counting from the end: `%w[A1 A2 A3].insert(-2, "X")` gives `["A1", "A2", "X", "A3"]`, and `insert(-1, x)` appends. If objects are given and the negative index reaches past the front, `insert` raises `IndexError`.
- Does compact! on a frozen array with no nils return nil or raise?It raises `FrozenError`. `compact!` checks that the receiver can be modified before scanning it, so a frozen array fails even when there is nothing to remove. `compact` works, because it returns a new array.
- Which of the pair should a helper that cleans an array it was passed use?Usually `compact`, returning the new array. The caller may keep using its original, and an in-place edit changes data the helper does not own. `compact!` fits arrays the method created itself, where saving a copy matters.
saying these in an interview costs you the question
- compact! always returns the array, so seats = seats.compact! is safe
- compact removes nil and false from the array
- compact! returns true or false to say whether it changed anything
- Every Array method that modifies the receiver ends with a bang
- delete_at raises IndexError when the index is out of range
- Every bang method returns nil when it has nothing to change