Ruby strings and symbols interview questions, with answers
Ruby has two kinds of text: mutable strings for data and immutable symbols for names. Knowing which to use, and how strings behave when they are changed, comes up in every Ruby interview, because it affects hash keys, memory and bugs where one change shows up somewhere unexpected.
The answers below cover symbols, formatting, frozen strings, bang methods, splitting and joining, regular expressions and encodings, with code run on Ruby 3.4. Then take the free Ruby diagnostic — ten questions across every Ruby topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.What is the difference between a string and a symbol in Ruby?
In short: A symbol such as :name is an immutable, unique identifier; a string is mutable text, and two equal string literals are separate objects.
Every occurrence of the symbol :done is the same object, so comparing symbols is as cheap as comparing two integers, and they are immutable. Strings are mutable text, and each string literal normally creates a new object. That makes symbols the natural choice for names: hash keys, method names passed to send or respond_to?, and fixed states such as :pending. Strings suit data that is built up, changed or shown to users. to_s and to_sym convert between them, but a symbol key and a string key are different keys in a hash.
s1 = :done s2 = :done p s1.object_id == s2.object_id p :done.to_s, "done".to_sym # true # "done" # :done
2.How do you format strings in Ruby?
In short: Use format (or the % operator) with placeholders such as %d, %s and %.2f, and rjust, ljust and center for padding.
format, also available as sprintf, builds a string from a template: %d formats an integer, %s a string, %.2f a float to two decimal places, and a width and 0 flag pad the result, so %05.2f gives 03.14. The % operator on a string does the same, as in "%s: %d" % ["a", 1]. For simple padding, rjust, ljust and center pad a string to a width with any character, which is handy for aligned tables. Most everyday text is built with double-quoted interpolation, "total: #{n}", which calls to_s on the embedded expression.
puts format("%05.2f", 3.14159) puts "7".rjust(3, "0") puts "ab".center(6, "*") # 03.14 # 007 # **ab**
3.What does freeze do to a Ruby string?
In short: freeze makes the object immutable, so any later attempt to modify it raises FrozenError; frozen? tells you whether it is frozen.
Calling freeze on an object prevents further changes to it: methods that modify it in place, such as upcase! or <<, raise FrozenError. Symbols, integers and nil are always frozen. The magic comment # frozen_string_literal: true at the top of a file makes every string literal in it frozen, which saves allocations and catches accidental mutation, and Ruby 3.4 warns when a literal without the comment is mutated, as a step toward frozen literals by default. freeze is shallow: a frozen array's elements can still change unless they are frozen too.
s = "hi".freeze p s.frozen? s.upcase! # true # raises FrozenError
4.What is the difference between upcase and upcase! in Ruby?
In short: upcase returns a new string and leaves the original alone; the bang version upcase! changes the string in place.
Many String methods come in pairs. The plain method, such as upcase, strip, sub or capitalize, returns a new string and leaves the receiver unchanged. The bang method, with a trailing !, modifies the receiver itself and returns it, or returns nil when there was nothing to change, so chaining bang methods is risky. The ! is a naming convention meaning "more dangerous than the plain version", usually because it mutates. Prefer the plain forms unless you are deliberately updating a string, for example in a loop that builds a large buffer.
s = "ruby" t = s.upcase s.capitalize! puts s, t # Ruby # RUBY
5.How do you split and join strings in Ruby?
In short: split breaks a string into an array on a separator or pattern; join combines an array's elements into one string.
"a,b,c".split(",") returns ["a", "b", "c"]. With no argument, split separates on runs of whitespace and ignores leading whitespace, and it also accepts a regular expression and a limit. The array method join does the reverse, with an optional separator. Related methods help with searching: include?, start_with? and end_with? return booleans, index returns a position or nil, and sub and gsub replace the first or every match. For comma-separated files with quoted fields, the csv library parses correctly where split would not.
csv = "a,b,c" parts = csv.split(",") p parts puts parts.join("-") # ["a", "b", "c"] # a-b-c
6.How do you use regular expressions with Ruby strings?
In short: Regex literals are written /pattern/; =~ returns a match's index, match returns MatchData, scan finds every match and gsub replaces them.
Ruby has regular expressions built into the language. str =~ /\d/ returns the index of the first match or nil, which also works as a condition. str.match(re) returns a MatchData object with captures by position or by name, scan returns every match as an array, and gsub replaces matches with a string, a hash or a block's result. After a successful match, $~ and $1 hold the last match data and first group. Ruby's regexes are Onigmo, with named groups, lookarounds and Unicode classes.
s = "call 555-1234" p s.scan(/\d+/) p s =~ /\d/ # ["555", "1234"] # 5
7.Why do length and bytesize differ for a Ruby string?
In short: length counts characters in the string's encoding, usually UTF-8, while bytesize counts the bytes those characters take.
Ruby strings carry an encoding, UTF-8 by default for source literals, and most methods work in characters: length, reverse, chars and slicing all respect multibyte characters. bytesize reports the raw size in bytes, which is what matters for network protocols and storage limits. In "café", é takes two bytes, so length is 4 and bytesize is 5. Encoding problems usually come from data read as binary or in another encoding; force_encoding relabels a string and encode converts it, and valid_encoding? checks it.
w = "café" p w.length, w.bytesize p w.chars.first(2) # 4 # 5 # ["c", "a"]
How the diagnostic asks it
One question from the Ruby bank, exactly as a sitting would show it. The bank has 3 on strings & symbols and 30 across Ruby.
What does this Ruby code print?
puts :ok.equal?(:ok) puts "ok".equal?("ok") puts :ok.to_s == "ok"
- 1true, true, true
- 2true, false, truecorrect
- 3false, false, true
- 4true, false, false
A symbol is an immutable, interned name: every occurrence of :ok in a program refers to the same object, so :ok.equal?(:ok), an identity test, is true. Each evaluation of a string literal creates a new String object, so the two "ok" strings are equal in value but are different objects, and equal? is false. Converting the symbol with to_s gives a String whose value is "ok", so == is true. The output is true, false, true. true three times assumes strings are interned like symbols, which a # frozen_string_literal: true comment can make happen for identical literals. Symbols are why hash keys such as :name are cheap.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 Ruby questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.