skip to content

In Ruby, how do format, sprintf and String#% build a fixed-width receipt line, and what happens on mismatched arguments?

level: middleimportance: should knowfreq 46%

answer

  1. format is an alias of sprintf
  2. %-18s left-justifies, %7.2f two decimals
  3. String#% takes an Array or Hash
  4. %<name>.2f vs %{name}
  5. too few: ArgumentError; missing key: KeyError

basics

~20 s

format and sprintf are the same Kernel method, and fmt % args calls it with the arguments; %-18s left-justifies in 18 characters and %7.2f prints two decimals. Too few arguments raise ArgumentError, a missing named key raises KeyError.

solid answer

~40 s

`Kernel#format` and `Kernel#sprintf` are two names for one method: a format string with `%[flags][width][.precision]type` specifications plus the values. `String#%` is the operator form: `"%05d" % 42` for one value, an Array for several (`"%-18s%7.2f" % [name, price]`), or a Hash for named references. For a receipt, `format("%-18s x%-2d %7.2f", name, qty, amount)` left-justifies the name in 18 characters, pads the quantity, and right-aligns the amount with two decimals. Named references come in two forms: `%<price>.2f` takes format flags, `%{name}` inserts the value as-is. Mismatches: too few arguments raise `ArgumentError` (`too few arguments`), a missing named key raises `KeyError`, and extra arguments are ignored unless warnings are on, when Ruby warns `too many arguments for format string`.

code

ruby · 7 lines
ruby
line = "%-18s x%-2d %7.2f"
puts format(line, "Flat white", 2, 7.6)
puts line % ["Croissant", 1, 2.95]
puts format("%<label>-20s%<total>10.2f", label: "TOTAL", total: 10.55)

format("%s %s", "Latte")    # ArgumentError: too few arguments
format("%<tip>.2f", {})      # KeyError: key<tip> not found

go deeper

for a junior

Recall that format, sprintf and String#% share one syntax, and read %-18s, %05d and %.2f without looking them up.

for a middle

Explain widths as minimums, the Array and Hash forms of String#%, the two named-reference styles, and which errors mismatched arguments raise.

for a senior

Watch for production traps: long values breaking column layouts, user text passed as the format string, and money printed from floats instead of exact amounts.

for a principal

Decide where formatting belongs: inline format strings, shared templates, or a presenter layer, and how locale-specific number formats are handled.

## One formatter, three spellings Ruby's formatter lives in `Kernel#sprintf`, and `Kernel#format` is the same method under another name. `String#%` calls the same machinery with the string as the format: ```ruby format("%05d", 42) # => "00042" sprintf("%05d", 42) # => "00042" "%05d" % 42 # => "00042" "%s x%d" % ["Latte", 2] # several values: pass an Array ``` `printf` on `Kernel` and `IO` prints the same result instead of returning it. ## Anatomy of a specification A format specification has the shape `%[flags][width][.precision]type`: | Part | Example | Effect | |---|---|---| | type `s` | `%s` | the argument's string form | | type `d` | `%d` | an integer | | type `f` | `%.2f` | a float with fixed decimals (default 6) | | flag `-` | `%-18s` | left-justify in the field | | flag `0` | `%05d` | pad with zeros instead of spaces | | flag `+` | `%+d` | always print the sign | | width | `%7.2f` | minimum field width, in characters | | precision on `s` | `%.10s` | at most 10 characters | | `%%` | `%d%%` | a literal percent sign | A width is a **minimum**: a value longer than the field is printed in full, which breaks column alignment rather than truncating. Precision on `%s` is the way to cap a field. ## Building a receipt line ```ruby Item = Data.define(:name, :qty, :price) items = [Item.new("Flat white", 2, 3.8), Item.new("Croissant", 1, 2.95)] items.each do |i| puts format("%-18s x%-2d %7.2f", i.name, i.qty, i.qty * i.price) end # Flat white x2 7.60 # Croissant x1 2.95 ``` 1. `%-18s` puts the name at the left of an 18-character field. 2. `x%-2d` prints the quantity left-justified in two characters after a literal `x`. 3. `%7.2f` right-aligns the amount in seven characters with two decimals, so the decimal points line up. ## Named references With a Hash argument, fields can be referenced by name: - `%<price>.2f` looks up `:price` and **applies** flags, width and precision. - `%{name}` looks up `:name` and inserts it **unformatted**; anything after the closing brace is literal text, so `"%{price}f"` prints the value followed by an `f`. ```ruby format("%<item>-18s%<price>7.2f", item: "Mocha", price: 4.2) "Order %{id} ready" % { id: 1042 } ``` Named references make long templates readable and let the same value appear twice without repeating arguments. ## When arguments do not match - **Too few positional arguments:** `format("%s %s", "Latte")` raises `ArgumentError` with `too few arguments`. - **Missing key:** `format("%<price>.2f", {})` raises `KeyError`, naming the missing key. - **Too many arguments:** ignored by default. With warnings enabled (`-w`) Ruby prints `too many arguments for format string`; in debug mode it raises. - **Wrong type:** `format("%d", "abc")` raises `ArgumentError`, because the string is not a valid integer. ## format versus interpolation Interpolation is enough when no alignment or precision is needed: `"Order #{id} ready"`. Reach for `format` when a value needs a fixed width, zero padding or a fixed number of decimals. A common bug shows why: - `(2 * 3.8).round(2).to_s` is `"7.6"`, because rounding returns a number and `Float#to_s` drops trailing zeros. - `format("%.2f", 2 * 3.8)` is `"7.60"`, the form a receipt needs. For money, many teams keep amounts as integer cents and format only at the edge, so the formatter never sees a binary-fraction rounding problem. ## format versus padding methods For a single column, `String#ljust`, `rjust` and `center` do the same padding job without a format string: - `"TOTAL".ljust(20)` pads to 20 characters on the right. - `"12.40".rjust(10)` pads on the left. - `"BEAN THERE".center(30)` pads both sides; `"-" * 30` builds a rule line. Like widths in `format`, these never truncate a longer string. `format` wins when several typed fields share one line; the padding methods read better for single labels.

  • What is the difference between %<price>.2f and %{price} in a Ruby format string?
    Both read `:price` from a Hash argument. `%<price>` is followed by normal flags, width, precision and a type, so `%<price>.2f` prints two decimals. `%{price}` inserts the value unformatted, and any following characters are literal text.
  • A menu item name is longer than the %-18s field. What does format print?
    The whole name. Width is a minimum, so the field grows and every following column shifts right. To cap it, add a precision such as `%-18.18s`, or truncate the name before formatting.
  • Why is passing user-supplied text as the format string itself a mistake?
    The text is parsed for `%` specifications, so a stray `%s` or `%<x>d` raises `ArgumentError` or `KeyError` and can crash the request. Pass user text as an argument, as in `format("%s", text)`, never as the format.

saying these in an interview costs you the question

  • format and sprintf differ: format returns a string, sprintf prints it
  • A width like %-18s truncates longer values to 18 characters
  • String#% accepts several values as separate arguments: fmt % a, b
  • %{price}.2f formats the price with two decimals
  • Too few format arguments are silently filled with empty strings