String Manipulation in Ruby

· 5 min read · Updated April 1, 2026 · beginner
ruby strings guides

Strings are one of the most used data types in Ruby. Whether you’re processing user input, building formatted output, or sanitizing data, you’ll work with strings constantly. This guide covers the essential techniques for creating, accessing, modifying, and formatting strings in Ruby.

Creating Strings

Ruby offers several ways to create strings:

# Double quotes — allows interpolation
name = "Alice"
greeting = "Hello, #{name}!"  # => "Hello, Alice!"

# Single quotes — literal, no interpolation
path = 'C:\Users\Alice'       # => "C:\\Users\\Alice"

# %q and %Q syntax
message = %Q(Multi-line string
with "quotes" and #{name})
heredoc = <<~TEXT
  Multi-line string
  without escaping
TEXT

# String.new (rarely needed)
empty = String.new

The #{} interpolation syntax inside double quotes lets you embed Ruby expressions directly into a string. Single quotes treat everything literally, which is useful for paths on Windows or strings containing #{.

String Indexing

Ruby strings are sequences of characters, and each character has a numeric index:

str = "hello"

str[0]   # => "h"
str[-1]  # => "o"  (negative indices count from the end)
str[1]   # => "e"
str[10]  # => nil  (out of bounds returns nil)

Ruby 3.0 introduced a significant change: indexing a string now returns a String instead of an Integer (the character code). This aligns with the principle that str[i] and str[i, 1] should behave the same way:

# Ruby 3.0+
str = "hello"
str[0]     # => "h"  (was 104 in Ruby 2.x)
str[0, 1]  # => "h"  (substring of length 1)
str[0..2]  # => "hel"

Slicing with []

The [] method supports several argument types for slicing:

str = "hello world"

str[6, 5]      # => "world"  (start index, length)
str[6..10]     # => "world"  (inclusive range)
str[6...11]    # => "world"  (exclusive range)
str["world"]   # => "world"  (substring search — returns nil if not found)
str[/wo+/]     # => "wo"     (regex match)

Ruby also provides slice and slice! (destructive):

str = "hello"
str.slice(0)      # => "h"   (returns a new string)
str               # => "hello"  (original unchanged)
str.slice!(0)     # => "h"
str               # => "ello"   (original mutated)

Substitution: gsub and sub

Ruby gives you two main tools for find-and-replace operations on strings.

gsub — Global Substitution

gsub replaces all occurrences of a pattern:

text = "foo bar foo baz foo"

text.gsub("foo", "qux")       # => "qux bar qux baz qux"
text.gsub("foo", "qux").object_id == text.object_id  # => false (returns new string)

You can also use a hash to map multiple replacements at once:

replacements = { "foo" => "qux", "bar" => "quux" }
text.gsub(/\w+/, replacements)  # => "qux quux qux baz qux"

With a block, the matched string is passed in and you can transform it:

"hello world".gsub(/\w+/) { |word| word.capitalize }
# => "Hello World"

sub — Single Substitution

sub replaces only the first occurrence:

"foo bar foo".sub("foo", "baz")  # => "baz bar foo"

Destructive Variants

Both gsub! and sub! modify the string in place and return the number of substitutions made (or nil if nothing was replaced):

str = "hello world"
str.gsub!("world", "ruby")
str  # => "hello ruby"

str = "foo bar foo"
str.sub!("foo", "baz")  # => "baz bar foo"
str  # => "baz bar foo"

Whitespace Handling: chomp and strip

chomp

Removes trailing record separators (usually newlines) from a string:

"hello\n".chomp        # => "hello"
"hello\r\n".chomp     # => "hello"
"hello".chomp         # => "hello"  (no trailing newline — no change)

You can also specify a custom separator:

"hello!".chomp("!")   # => "hello"

The destructive variant chomp! modifies in place.

strip

Removes leading and trailing whitespace:

"  hello  ".strip          # => "hello"
"\thello\r\n".strip        # => "hello"

Ruby also provides lstrip and rstrip for one-sided trimming:

"  hello  ".lstrip   # => "hello  "
"  hello  ".rstrip   # => "  hello"

Case Conversion

Ruby provides four case-conversion methods:

"Hello World".upcase    # => "HELLO WORLD"
"HELLO world".downcase  # => "hello world"
"hello world".capitalize  # => "Hello world"  (first char up, rest down)
"Hello World".swapcase    # => "hELLO wORLD"

All have destructive variants (upcase!, downcase!, etc.) that modify in place:

str = "Hello"
str.upcase!
str  # => "HELLO"

Splitting and Joining

split

Breaks a string into an array of substrings:

"apple,banana,cherry".split(",")
# => ["apple", "banana", "cherry"]

"one   two  three".split(/\s+/)
# => ["one", "two", "three"]

"hello".split("")
# => ["h", "e", "l", "l", "o"]

You can limit the number of splits:

"a,b,c,d".split(",", 2)
# => ["a", "b,c,d"]

join

The inverse of split — combines an array into a single string:

["apple", "banana", "cherry"].join(",")
# => "apple,banana,cherry"

["hello", "world"].join(" ")
# => "hello world"

["a", "b", "c"].join
# => "abc"

A common idiom is split followed by map and join to transform every element:

"one two three".split.map(&:capitalize).join(", ")
# => "One, Two, Three"

String Interpolation

Interpolation lets you embed Ruby expressions directly inside a string. It only works with double quotes:

name = "Alice"
age = 30

"#{name} is #{age} years old"
# => "Alice is 30 years old"

'#{name} is #{age} years old'
# => "\#{name} is \#{age} years old"  (literal, no interpolation)

You can call methods and use complex expressions inside #{}:

"I have #{[1, 2, 3].length} items"
# => "I have 3 items"

"Sum: #{1 + 2 + 3}"
# => "Sum: 6"

For cleaner output with numbers, Kernel#format or String#% is often preferable to inline arithmetic:

price = 19.99
format("Price: $%.2f", price)
# => "Price: $19.99"

"Price: $#{'%.2f' % price}"
# => "Price: $19.99"

Encoding

Ruby strings carry an encoding. Misaligned encodings are a common source of errors when working with non-ASCII text or file I/O.

Checking and Setting Encoding

str = "café"
str.encoding          # => #<Encoding:UTF-8>

str.encode("ISO-8859-1")
# => "caf\xe9"  (in ISO-8859-1 encoding)

Force Encoding

raw = "café".b  # .b forces binary (ASCII-8BIT) encoding
raw.encoding    # => #<Encoding:ASCII-8BIT>

Common Encoding Gotcha

Reading a file without specifying encoding can cause Encoding::CompatibilityError:

# Ruby 3.0+
File.read("data.txt", encoding: "UTF-8")
# or
File.read("data.txt", encoding: "r:UTF-8")  # transcode from UTF-8

The Encoding.default_external setting controls the default for new strings. Set it in your application entry point:

Encoding.default_external = Encoding::UTF_8

Validating Encoding

"café".valid_encoding?   # => true
"\xff".valid_encoding?  # => false (invalid UTF-8 byte)

See Also