rubyguides

String#each_char

str.each_char { |char| block } -> str

each_char and each_line are String methods that iterate over the characters or lines of a string, respectively. They are useful for processing text data character-by-character or line-by-line when you need fine-grained control over text parsing.

Syntax

str.each_char { |char| block } -> str
str.each_line(separator=$/) { |line| block } -> str

Parameters

each_char

No parameters. Yields each character in the string as a single-character string.

each_line

ParameterTypeDefaultDescription
separatorString$/ (newline)The line separator. Can be any string. Pass an empty string for paragraph mode.

Examples

each_char basic usage

"hello".each_char { |c| print c, " " }
# => h e l l o

This is the simplest form of character iteration, and it is a good reminder that each_char works one character at a time. The block receives a one-character string, so the code can react to each piece of text without splitting the whole string into an array first. That lazy behavior means the iteration does not allocate an intermediate array, which can matter when the string is large. The method is equally predictable with short strings, so the behavior stays consistent regardless of input size.

each_char building a character array

chars = []
"ruby".each_char { |c| chars << c }
chars
# => ["r", "u", "b", "y"]

Collecting the characters into an array makes the shape of the result easy to inspect. That is useful when you need to keep the original order but also want random access or later transformation steps. While chars would give you the same array directly, using each_char lets you filter or transform characters before they land in the collection.

each_line basic usage

text = "line one\nline two\nline three"
text.each_line { |l| puts l }
# line one
# line two
# line three

The line-based example sits next to the character version because the two methods solve neighboring problems. If the text is best handled as sentences or records, each_line is often the better match, but each_char is the one that makes single-character work readable. Both iterators return an Enumerator when called without a block, so they can feed into the same chaining patterns.

each_line with custom separator

"a,b,c".each_line(",") { |part| puts part }
# a
# b
# c

Using a custom separator is a practical reminder that each_line is flexible enough for more than newline-delimited text. It can still show why a character-by-character approach is sometimes too small a unit for the job.

each_line with empty separator for paragraphs

Paragraph mode is useful when the input naturally groups into blocks separated by blank lines. It treats consecutive newlines as a delimiter, which makes it a natural fit for structured text like email bodies, markdown paragraphs, or log entries:

text = "para one\n\npara two\n\npara three"
text.each_line("") { |p| puts p; puts "---" }

Paragraph mode is useful when the input naturally groups into blocks. It is a nice example of why the string iteration methods are worth learning together: the surrounding code can stay compact even when the text format changes.

Paragraph mode is a good example of why the string iteration methods are worth learning together. The surrounding code can stay compact even when the text format changes, because you can swap the iterator without rewriting the rest of the pipeline.

Common Patterns

Character frequency counting

frequency = Hash.new(0)
"hello world".each_char { |c| frequency[c] += 1 }
frequency
# => {"h"=>1, "e"=>1, "l"=>3, "o"=>2, " "=>1, "w"=>1, "r"=>1, "d"=>1}

Counting characters is one of the most direct uses for this method. The hash keeps the running total, while each_char provides a clean stream of input that does not need any pre-processing beyond the iteration itself. The default value of 0 from Hash.new(0) makes the counting logic especially concise, since every character starts with a tally of zero automatically.

Processing file contents line by line

File.read("data.txt").each_line.with_index do |line, i|
  puts "#{i + 1}: #{line}"
end

Reading line by line is often the better choice for files because it avoids loading every byte at once. The example sits here to show that each_char is not the only iterator in the family, and choosing the right one is part of writing maintainable text code.

Using Enumerator for lazy iteration

enum = "hello".each_char
enum.next
# => "h"
enum.next
# => "e"
enum.peek
# => "l"

The enumerator form is useful when you want to advance through the string later rather than all at once. That can be a good fit for parser code or small state machines where the next character depends on what came before.

Transforming each character

"hello".each_char.map { |c| c.upcase }
# => ["H", "E", "L", "L", "O"]

Mapping after each_char shows that the method can feed the rest of Ruby’s collection toolkit. The result is still a stream of single-character strings, but now the characters can be transformed just like any other enumerable data.

Performance Note

For simple character iteration, each_char is more efficient than splitting into an array first (string.chars.each). The method returns an Enumerator when called without a block, allowing method chaining with other Enumerable methods.

When processing large files, consider reading line-by-line with File.foreach instead of loading the entire file into memory first.

Choosing between char and line iteration

There is a simple rule of thumb here: use each_char when the meaning lives in the individual character, and use each_line when the structure lives in the line. That split keeps the code aligned with the shape of the data instead of forcing everything through the same tiny unit.

each_char works best when the logic depends on individual characters, such as counting symbols, validating a narrow input format, or building a custom parser. If you care more about record boundaries or readable text chunks, each_line is usually the better choice because it keeps the original line structure intact.

The enumerator form is also worth remembering. Calling each_char without a block lets you chain other enumeration methods, which can be useful when you want to filter or map characters before turning them into a final result.

Iterating with index

If you need both the character and its position, chain each_char with with_index:

"hello".each_char.with_index { |c, i| puts "#{i}: #{c}" }
# 0: h
# 1: e
# 2: l
# 3: l
# 4: o

This is a common pattern when building parsers or validators that need to report the position of a problem character alongside the character itself.

See Also