rubyguides

String#bytes

Basic Usage

bytes returns an array of integers, one per byte in the string:

"hello".bytes
# => [104, 101, 108, 108, 111]

Each integer is a value between 0 and 255. No arguments, no block — just the array.

Signature

The method takes no arguments and returns a flat array of integers between 0 and 255. Since the return value is always an Array, you can chain it with other array methods like sum, include?, or each right on the result.

str.bytes -> array

The receiver is any String object. The method takes no arguments and returns an Array of integers, one per byte.

Multi-byte characters are not single bytes

This is where bytes trips up many developers. A UTF-8 character can take 1 to 4 bytes in UTF-8, so the raw byte count and the character count often differ. The string "café" has 4 characters but 5 bytes:

"café".bytes
# => [99, 97, 102, 195, 169]

"café".chars.count   # => 4
"café".bytes.count   # => 5

The é (U+00E9) encodes as two bytes: 195 and 169. If you loop over bytes expecting one element per character, you will get surprises.

Multi-byte example with CJK characters

For Japanese characters, each kanji in UTF-8 occupies 3 bytes. That means a string of 3 characters expands to 9 bytes, showing how the gap between character count and byte count grows quickly with non-Latin scripts:

s = "日本語"
s.bytes
# => [230, 151, 165, 230, 152, 165, 230, 156, 165]

s.chars.count   # => 3
s.bytes.count   # => 9

Encoding changes the byte values

The same character sequence produces different bytes depending on the encoding. UTF-8 uses a variable-width scheme where ASCII characters stay at 1 byte and non-ASCII characters expand. Latin-1 and other single-byte encodings keep every character to exactly 1 byte.

"é".encode("UTF-8").bytes
# => [195, 169]

"é".encode("ISO-8859-1").bytes
# => [233]

UTF-8 uses multiple bytes for non-ASCII characters. ISO-8859-1 (Latin-1) uses one byte per character. Always check str.encoding before interpreting byte values.

bytes vs codepoints vs chars

These three methods get confused constantly because they all return array-like results from a string, but each answers a different question about the underlying data.

"café".chars       # => ["c", "a", "f", "é"]
"café".codepoints  # => [99, 97, 102, 233]
"café".bytes       # => [99, 97, 102, 195, 169]

chars returns an array of single-character strings, one per visible character. codepoints gives you Unicode integer values, also one per character — é is codepoint 233 regardless of encoding. bytes gives you the raw byte values, where non-ASCII characters like é span multiple elements (195 and 169 in UTF-8). The distinction matters whenever you are counting or slicing by position rather than iterating over the full array.

Iterating with each_byte

If you need to process each byte individually instead of getting the full array at once, each_byte is an explicit iterator that yields one integer at a time:

byte_values = []
"hello".each_byte { |b| byte_values << b }
byte_values
# => [104, 101, 108, 108, 111]

Both each_byte and bytes without a block return the same values. Using each_byte makes the iteration intent clear in your code because the block signals that you are processing one value at a time rather than building a collection upfront.

Checking bytesize separately

If you only need the count of bytes, bytesize (or bytesize? in newer Ruby) is more direct:

"café".bytesize
# => 5

"hello".bytesize
# => 5

Calling bytes.count gives the same number but constructs an intermediate array before counting it, which costs memory for large strings. For a quick byte count without the allocation overhead, reach for bytesize instead.

Working with binary data

bytes is useful when you need to inspect or manipulate binary data stored in a string:

data = "\xFF\x00\x80"
data.bytes
# => [255, 0, 128]

Once you have the byte array, you can check for specific byte values or patterns with any Array method. For example, include? tells you whether a particular byte appears anywhere in the data, which is useful for detecting magic numbers or delimiters in binary file headers.

# Check if a specific byte value is present
data.bytes.include?(255)
# => true

Ruby strings can hold arbitrary binary data when marked with the BINARY encoding, also known as ASCII-8BIT. This encoding tells the VM to treat every byte as a standalone value rather than interpreting multi-byte sequences as characters. Calling .b on any string returns a binary-encoded copy:

binary_string = "hello".b
binary_string.encoding
# => #<Encoding:ASCII-8BIT>

binary_string.bytes
# => [104, 101, 108, 108, 111]

When you call .b on a string, Ruby marks it as ASCII-8BIT without changing the underlying bytes. This encoding tells Ruby to treat each byte independently rather than interpreting multi-byte sequences. The bytes method then returns exactly the raw values you expect.

Practical example: checksum calculation

A common use case is computing a simple byte-sum checksum:

def byte_sum(str)
  str.bytes.sum
end

byte_sum("hello")
# => 532  (104 + 101 + 108 + 108 + 111)

A byte-sum checksum is fast and easy to implement, but it only catches single-bit errors. For stronger integrity guarantees in production code, use a cryptographic hash like SHA-256 instead.

See Also