skip to content

In Ruby, what do `puts nil`, `puts []`, `puts [1, [2, nil]]`, `print nil` and `p nil` each write to standard output?

level: juniorimportance: should knowfreq 42%

answer

  1. nil.to_s is empty
  2. empty text still gets a newline
  3. empty array: zero iterations
  4. puts recurses into nested arrays
  5. p shows nil literally

basics

~20 s

puts nil writes an empty line, puts [] writes nothing, puts [1, [2, nil]] writes 1, 2 and an empty line, print nil writes nothing, and p nil writes the text nil plus a newline.

solid answer

~40 s

`puts` converts a non-array argument with `to_s`; `nil.to_s` is `""`, and an empty string still gets a newline, so `puts nil` writes a blank line. An array is walked element by element, recursing into nested arrays, so `puts [1, [2, nil]]` writes `1`, `2` and a blank line - and `puts []` iterates zero times and writes nothing at all. `print nil` writes `nil.to_s`, the empty string, with no newline, so nothing appears. `p nil` writes `nil.inspect`, which is the three letters `nil`, followed by a newline, and returns `nil`. The difference matters when a parser yields `nil`: `puts` shows a blank line you can miss, while `p` names the value.

code

ruby · 14 lines
ruby
puts nil            # (blank line)
puts []             # (nothing)
puts [1, [2, nil]]  # 1
                    # 2
                    # (blank line)
print nil           # (nothing)
p nil               # nil

print [1, nil, "x"], "\n"   # [1, nil, "x"]

loop_list = [1]
loop_list << loop_list
puts loop_list      # 1
                    # [...]

go deeper

for a junior

Recall that nil.to_s is empty while nil.inspect is the word nil, and that puts writes a newline even for empty text but writes nothing for an empty array.

for a middle

Explain the puts algorithm: strings as-is, arrays recursed element by element, everything else through to_s, and a newline only when the text does not already end with one.

for a senior

Recognise when blank or missing output hides nil or empty results in a log or a captured-output test, and switch to p or inspect so the value is named.

for a principal

Push teams toward output that names values unambiguously in diagnostics, so an empty collection, a nil and an empty string can never be confused during an incident.

This question checks whether you know the exact rules `Kernel#puts`, `Kernel#print` and `Kernel#p` follow for **nil**, **empty values** and **arrays** - the inputs that make debugging output misleading. Each rule falls out of two facts: which conversion the method uses, and how `puts` treats arrays. ## The conversions involved - **`nil.to_s`** returns the empty string `""`. - **`nil.inspect`** returns the string `nil`. - **`puts`** and **`print`** use `to_s` for anything that is not already a string; `p` uses `inspect`. - **Arrays** get special treatment in `puts` only. `print` and `p` convert the whole array to one string, and `Array#to_s` is an alias of `Array#inspect`, so both show brackets. ## How puts decides what to write The C implementation of `IO#puts` (which `Kernel#puts` forwards to on standard output) applies these rules to each argument in turn: 1. If the argument is a **String**, it is the line to write. 2. If it **converts to an array**, each element is passed back through `puts` recursively, and nothing else is written for the array itself. 3. Otherwise the line is the argument's **`to_s`**. 4. If the line is **empty**, a single newline is written. 5. Otherwise the line is written, plus a newline **only if it does not already end with one**. With no arguments at all, `puts` writes one newline. The newline `puts` adds is always the character `"\n"`. It is not the output record separator `$\`, which only `print` appends. So a program that set `$\` (itself a legacy practice) still gets exactly one `"\n"` per line from `puts`, while `print` would add `$\` after its last argument. And because `Kernel#puts` forwards to `$stdout.puts`, the same rules apply to `IO#puts` on a file: the conversion and newline logic lives in the `puts` method itself, not in the destination. ## Walking through the five calls | Call | Rule applied | Output | |---|---|---| | `puts nil` | `nil.to_s` is `""`, empty line rule | one blank line | | `puts []` | array rule, zero elements | nothing | | `puts [1, [2, nil]]` | array rule, recursing | `1`, `2`, then a blank line | | `print nil` | `nil.to_s`, no newline | nothing | | `p nil` | `nil.inspect` plus newline | `nil` | Two of those surprise people. `puts []` produces no output even though `puts` with no arguments produces a newline - the empty array is an argument, and iterating it zero times writes nothing. And `puts [nil]` writes a blank line rather than skipping the element, because each element gets the empty-line rule. ## Related traps - **Strings that already end in a newline.** `puts "line\n"` writes one line, not a line and a blank line. `print "line\n"` writes the string as it is. - **Recursive arrays.** An array that contains itself does not loop forever: `puts` writes `[...]` for the recursive reference. - **`print` with an array** writes the `inspect` form on one line: `print [1, nil, "x"]` writes `[1, nil, "x"]`, strings quoted and `nil` spelled out, because `Array#to_s` is `inspect`. - **Return values.** All three calls on `nil` return `nil`, but for different reasons: `puts` and `print` always return `nil`, while `p` returns its argument, which here happens to be `nil`. ## Why interviewers ask it A train-timetable parser that maps rows to departure times might return `nil` for a row it could not parse. Logging those values with `puts` produces blank lines that are easy to overlook in a long listing, and an empty result array produces no output at all - so "nothing printed" can mean either "no rows" or "every row was nil". Using `p` settles it immediately: `p []` prints `[]`, `p [nil, nil]` prints `[nil, nil]`, and `p nil` prints `nil`. The practical rule is short: 1. Use `puts` when the reader is a person and blank lines are harmless. 2. Use `p` whenever the difference between `nil`, `""` and `[]` matters, because only `inspect` spells all three out. 3. Remember that `puts` flattens nested arrays, so it hides structure; `p` and `pp` keep the brackets that show it. Knowing these rules also explains test failures around captured output: an assertion expecting `"\n"` from `puts []` will fail, because the method wrote nothing.

  • Why does `print [1, nil, "x"]` show brackets and quotes when `puts` of the same array does not?
    `print` converts its argument with `to_s`, and `Array#to_s` is an alias of `Array#inspect`, so the whole array becomes one string like `[1, nil, "x"]`. `puts` special-cases arrays: it writes each element separately with that element's own `to_s`, so the brackets and quotes never appear.
  • In Ruby, what does `puts "done\n"` write, and why?
    It writes `done` and a single newline. `puts` only appends a newline when the text does not already end with one, so a string that already ends in a newline is written once, without an extra blank line.

saying these in an interview costs you the question

  • puts nil prints the word nil, the same as p nil.
  • puts [] prints an empty line, like puts with no arguments.
  • print on an array writes each element on its own line.
  • puts always adds a newline, so a string ending in a newline gives a blank line.
  • puts skips nil elements inside an array.