skip to content

Core Value Types

Ruby's everyday value classes: Integer, Float and Rational, Symbol, nil, true and false, and the to_s, to_str and Integer() protocols that convert between them. Interviewers probe the surprises.

on this pageshow

explore

questions

19

In Ruby, why does "Qty: " + 6 raise TypeError, and what are the idiomatic ways to build that string?

level: juniorimportance: must knowfreq 62%

answer

  1. wants a real String
  2. Integer has to_s but not to_str
  3. no implicit conversion of Integer into String
  4. reverse order asks Integer to coerce
  5. two numeric strings concatenate

basics

~20 s

String#+ accepts only a String or an object with to_str, and Integer has no to_str, so Ruby raises TypeError: no implicit conversion of Integer into String. Convert explicitly with 6.to_s, or build the string with interpolation.

solid answer

~40 s

Ruby does not convert types behind your back in most operators. `String#+` insists on a String: it calls the implicit converter `to_str` on its argument, and `Integer` defines only the explicit `to_s`, so `"Qty: " + 6` raises `TypeError` with `no implicit conversion of Integer into String`. The fixes all convert explicitly: `"Qty: " + qty.to_s`, `"Qty: #{qty}"`, whose interpolation calls `to_s`, or `format("Qty: %d", qty)`. The reverse order fails differently: `6 + "3"` raises `TypeError` with `String can't be coerced into Integer`, because `Integer#+` asks its argument to coerce itself. To add, parse first: `6 + Integer("3")`. JavaScript would silently concatenate here; Ruby, like Python, refuses.

code

ruby · 10 lines
ruby
qty = 6

"Qty: " + qty            # TypeError: no implicit conversion of Integer into String
"Qty: " + qty.to_s       # => "Qty: 6"
"Qty: #{qty}"            # => "Qty: 6"
format("Qty: %d", qty)   # => "Qty: 6"

qty + "3"                # TypeError: String can't be coerced into Integer
qty + Integer("3")       # => 9
"6" + "3"                # => "63", two CSV cells concatenated, not added

go deeper

for a junior

Recall that + does not mix strings and numbers, quote the no implicit conversion message, and show to_s and interpolation as fixes.

for a middle

Explain that the left operand's method decides the rule: String#+ needs to_str, Integer#+ needs coerce, which is why the two messages differ.

for a senior

Point to the production trap of numeric strings from files or params concatenating silently, and parse at the boundary before doing arithmetic.

for a principal

Defend strong typing in core operators as a design choice that surfaces mistakes early, and set conventions for where input text becomes numbers.

## Why + refuses In Ruby, `+` is an ordinary method call on the left operand: `"Qty: " + 6` is `"Qty: ".+(6)`. `String#+` concatenates two strings, so it must turn its argument into a String first. It does not call just any conversion method. It calls **`to_str`**, the *implicit* conversion, which a class defines only when its objects really are strings in all but name. - `String` defines `to_str`, returning itself. - `Integer`, `Float`, `Symbol`, `Array` and `nil` do **not** define `to_str`. - They all define `to_s`, the *explicit* conversion you call yourself when you want a textual representation. So `String#+` finds no `to_str` on `6` and raises `TypeError` with the message `no implicit conversion of Integer into String`. The message names the missing *implicit* conversion; it is not saying `6` cannot be printed. ## The two error messages Which message you get depends on which operand's method runs: | Expression | Method that runs | Result | |---|---|---| | `"Qty: " + 6` | `String#+` | `TypeError`: `no implicit conversion of Integer into String` | | `6 + "3"` | `Integer#+` | `TypeError`: `String can't be coerced into Integer` | | `6 + nil` | `Integer#+` | `TypeError`: `nil can't be coerced into Integer` | | `"6" + "3"` | `String#+` | `"63"`, concatenation, no error | Numeric operators handle unknown operands through the **coerce** protocol: `Integer#+` asks the other object to `coerce` itself into a compatible number. A `String` does not respond to `coerce`, so the attempt fails with the second message. ## Idiomatic fixes Every fix makes the conversion explicit: 1. **Call `to_s`**: `"Qty: " + qty.to_s`. Clear, but repetitive when there are many pieces. 2. **Interpolate**: `"Qty: #{qty}"`. Interpolation calls `to_s` on the embedded value, so it works for any object and is the most common choice. 3. **Format**: `format("Qty: %d", qty)` when you need padding or number formatting. 4. **Parse the other way** when you want arithmetic: `6 + Integer("3")` is `9`. ## Other operators follow the same rule The same implicit protocol governs other core operators, so the pattern generalises: - `[1] + 2` raises `TypeError` with `no implicit conversion of Integer into Array`: `Array#+` wants `to_ary`. - `"ab" * "2"` raises `TypeError` with `no implicit conversion of String into Integer`: `String#*` wants an integer count. - `"total" + :qty` raises `no implicit conversion of Symbol into String`: `Symbol` has `to_s` but no `to_str`. One operator is a trap because it does **not** raise. `String#<<` accepts an `Integer` and treats it as a **codepoint**, so appending `6` to a label adds the invisible control character U+0006 instead of the digit. Convert with `to_s` before appending a number. ## Strictness is deliberate Ruby is dynamically typed but **strongly** typed: values keep their types, and most core methods refuse to guess. The comparison interviewers raise is JavaScript, where `"Qty: " + 6` produces `"Qty: 6"` and `"6" * 1` produces `6`; Python refuses like Ruby. Ruby's refusal surfaces a mistake at the line that made it rather than as a wrong value later. - An operator that accepts "anything that can become a string" would also accept `nil`, turning missing data into empty text. - Implicit conversion is reserved for objects that *are* the target type, so a successful `+` tells you both operands were strings. ## Worked example: a grocery-order CSV A grocery-order file has columns for item, quantity and extra quantity. Every cell a CSV parser returns is a `String`, or `nil` for a blank cell, never an `Integer`. - `row[1] + row[2]` with cells `"6"` and `"3"` returns `"63"`: two strings concatenate without complaint, which is a silent bug rather than an error. - `"Total: " + (Integer(row[1]) + Integer(row[2]))` raises `TypeError`, because the sum is an `Integer`. - Correct: parse each cell with `Integer(...)`, add the integers, then interpolate: `"Total: #{total}"`. A blank cell adds one more trap: `Integer(nil)` raises `TypeError` with `can't convert nil into Integer`, so decide how blanks are handled before summing. ## Summary - `String#+` needs a String or an object with `to_str`; numbers do not qualify. - `Integer#+` needs a number or an object with `coerce`; strings do not qualify. - Convert explicitly with `to_s`, interpolation or `Integer()`, and remember that two numeric strings concatenate instead of adding.

  • In Ruby, why is the error for 6 + "3" worded differently from "Qty: " + 6?
    A different method runs. `6 + "3"` calls `Integer#+`, which handles non-numeric operands by asking them to `coerce`; `String` has no `coerce`, so it raises `String can't be coerced into Integer`. `"Qty: " + 6` calls `String#+`, which needs `to_str`, hence `no implicit conversion of Integer into String`.
  • In Ruby, what does label << 6 do when label is a String?
    It does not raise. `String#<<` treats an `Integer` argument as a codepoint and appends that character, so `6` adds the invisible control character U+0006 rather than the digit. Append `6.to_s`, or interpolate, when you mean the text "6".

saying these in an interview costs you the question

  • String#+ calls to_s on its argument, so any object can be appended
  • Ruby converts 6 to "6" automatically in a + expression
  • "6" + "3" returns 9 in Ruby
  • The TypeError means the integer cannot be printed
  • 6 + "3" and "Qty: " + 6 raise the same error message
open as a page

In Ruby, which values are falsy, and does a condition treat 0, an empty string or an empty array as true?

level: juniorimportance: must knowfreq 82%

basics

~10 s

Only nil and false are falsy in Ruby. Every other object, including 0, 0.0, "", [], {} and the string "false", is truthy, so test emptiness or zero explicitly with empty?, zero? or nil?.

open as a page

In Ruby, why does 0.1 + 0.2 == 0.3 return false, and how should you compare or compute such values instead?

level: juniorimportance: must knowfreq 75%

basics

~20 s

Float stores IEEE 754 binary doubles, and 0.1, 0.2 and 0.3 have no exact binary form, so the sum is 0.30000000000000004. Compare Floats with a tolerance, or compute with Rational, BigDecimal or integer cents when exactness matters.

open as a page

In Ruby, how does a Symbol such as :delivered differ from the String "delivered" in identity, mutability and typical use?

level: juniorimportance: must knowfreq 80%

basics

~20 s

A Symbol is an immutable, interned name: every :delivered is the same object with one object_id, while each evaluation of the literal "delivered" builds a new String. Use symbols for identifiers and fixed flags, strings for text.

open as a page

In Ruby, how do Integer("12abc") and "12abc".to_i differ, and when should parsing a quantity raise instead of returning 0?

level: middleimportance: must knowfreq 66%

basics

~10 s

"12abc".to_i returns 12 and returns 0 when nothing parses. Integer("12abc") raises ArgumentError, and Integer(nil) raises TypeError. Use Integer() or Float() when bad input must be reported; exception: false returns nil instead.

open as a page

In Ruby 4.0, what does undefined method 'upcase' for nil (NoMethodError) tell you, and how do you trace where the nil came from?

level: middleimportance: must knowfreq 68%

basics

~20 s

It means upcase was called on nil: an expression expected to return a String returned nil. Use error_highlight's carets to find which call on the line received nil, then trace that value back to the method that produced it.

open as a page

In a Ruby 4.0 service quoting foreign-exchange amounts, how would you use BigDecimal to convert and round to cents correctly?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Add bigdecimal to the Gemfile, since it is a bundled gem from Ruby 3.4, build amounts and rates from Strings, multiply as BigDecimal and round once at the end with an explicit mode such as round(2, half: :even).

open as a page

In Ruby, what classes are nil, true and false instances of, why is there no Boolean class, and what does !!value return?

level: juniorimportance: should knowfreq 50%

basics

~20 s

nil, true and false are the only instances of NilClass, TrueClass and FalseClass, each inheriting directly from Object, so Ruby has no shared Boolean class. !!value returns true for any truthy value and false for nil or false.

open as a page

In Ruby 4.0, what class does (1..30).reduce(:*) return, and what happened to Fixnum and Bignum?

level: juniorimportance: should knowfreq 45%

basics

~10 s

It returns Integer. Ruby has one arbitrary-precision Integer class that grows instead of overflowing; Fixnum and Bignum were unified into Integer in Ruby 2.4 and their deprecated constants were removed in 3.2.

open as a page

In Ruby, what do Kernel#Array() and Kernel#String() return for nil, a single value and an existing array, and why use them?

level: middleimportance: should knowfreq 40%

basics

~10 s

Array(x) tries to_ary, then to_a, then wraps: Array(nil) is [], Array("a") is ["a"] and an array comes back as is. String(x) tries to_str, then to_s: String(nil) is "" and String(6) is "6".

open as a page

In Ruby, what do nil.to_s, nil.to_a, nil.to_i and nil.inspect return, and when does relying on them hide a bug?

level: middleimportance: should knowfreq 45%

basics

~20 s

nil.to_s returns "", nil.to_a returns [], nil.to_h returns {}, nil.to_i returns 0, nil.to_f returns 0.0 and nil.inspect returns "nil". They make blanks safe to print, but erase the difference between missing and empty or zero.

open as a page

In Ruby, how do Kernel#rand, Random.rand and Random.new(seed) differ, and how do you make random results reproducible?

level: middleimportance: should knowfreq 30%

basics

~20 s

Kernel#rand and Random.rand draw from Ruby's shared default generator, while Random.new(seed) creates an independent one. The same seed replays the same sequence, and shuffle and sample accept random: for repeatable results. None of them is fit for secrets.

open as a page

In Ruby, what do Float#round, floor and ceil return when you pass a digits argument or the half: keyword?

level: middleimportance: should knowfreq 40%

basics

~20 s

With no digits, zero or negative digits they return an Integer; with positive digits a Float. round breaks ties away from zero unless half: :even or half: :down is given; floor and ceil always move toward negative or positive infinity.

open as a page

In Ruby 4.0, how do to_sym, Symbol#to_s, Symbol#name and %i[] literals convert between strings and symbols?

level: middleimportance: should knowfreq 45%

basics

~20 s

String#to_sym (alias intern) finds or creates the symbol; Symbol#to_s (alias id2name) returns a new String that warns on mutation since Ruby 3.4; Symbol#name returns a cached frozen String; %i[a b] builds [:a, :b], and %I interpolates.

open as a page

In Ruby, what separates explicit to_s, to_a and to_h from implicit to_str, to_ary and to_hash, and when should a class define the implicit ones?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Explicit converters such as to_s, to_a and to_h are representations you ask for, and most classes may define them. Implicit ones such as to_str, to_ary and to_hash claim the object is that type, so core methods call them automatically.

open as a page

In a Ruby service where NoMethodError on nil keeps surfacing far from its cause, how do you stop nil spreading instead of silencing the errors?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Decide at each boundary whether a missing value is legal. If not, raise there with a clear error; if so, normalise it to "" or a null object. Blanket rescues and NilClass patches only move the failure.

open as a page

In Ruby 4.0, can calling to_sym on untrusted input such as a delivery status parameter leak memory, and how would you guard against it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Mostly not since Ruby 2.2: symbols created at runtime by to_sym are garbage-collected once unreferenced. Symbols from source code, and dynamic ones pinned as method names, live for the whole process, so map untrusted input through an allowlist instead.

open as a page

In Ruby, how do Rational values like 3r and 0.1r stay exact, and what do quo, fdiv and Float#to_r return?

level: middleimportance: nice to knowfreq 25%

basics

~10 s

A Rational stores a reduced Integer numerator and denominator, so 1/3r + 1/6r is exactly (1/2). 7.quo(2) returns (7/2), 7.fdiv(2) returns 3.5, and 0.1.to_r returns the stored double's exact fraction, not (1/10).

open as a page

In Ruby, how does Numeric#coerce let 6 * price work when price is a custom Money object, and what fails without it?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Integer#* does not know Money, so it calls price.coerce(6), expects a two-element array [x, y], and evaluates x * y. Without coerce, Ruby raises TypeError: Money can't be coerced into Integer.

open as a page