String#chop
chop removes the last character from a string, or the CRLF (\r\n) pair if the string ends with one. Unlike chomp, which only strips line endings, chop removes any trailing character unconditionally.
That means chop is useful when you truly want to drop the last character, not just a newline. It is a small method, but the difference matters when the final character could be meaningful data.
The examples below show that difference from a few angles: simple trimming, the bang version, and the cases where chop can remove more than you expected. That context helps explain why chomp is often the safer choice for line-oriented text.
Signature
str.chop → new_string
Returns: A new string with the last character removed. The method always produces a new string object rather than modifying the receiver in place, so the original remains unchanged. This is the same non-mutating convention followed by most Ruby string methods that have no ! suffix.
Basic Behavior
# Remove the last character
"hello".chop
# => "hell"
# Handles CRLF as a pair
"hello\r\n".chop
# => "hello"
# CRLF counts as two characters removed
"hello\r\n\r\n".chop
# => "hello\r\n"
When the string ends with \r\n, both characters are removed together as a single unit. This is the one special case where chop removes two characters instead of one; in all other situations, only the last character is dropped. The CRLF handling makes chop slightly smarter than a naive str[0..-2] slice when dealing with Windows-style line endings. The empty string case is also handled gracefully: calling chop on "" returns "", avoiding an error that a range-based slice might produce.
This behaviour is consistent, but it can be surprising if you expected a newline-only trim. The method always looks at the last one or two bytes of the string, and it always removes something if there is anything to remove. For line-oriented text, chomp is usually the safer choice because it only strips recognised line endings.
# Only removes the last character
"hello\n".chop
# => "hello\n" (LF is just another character)
# Empty string stays empty
"".chop
# => ""
The example with "hello\n" shows an important edge case: the newline character \n is treated as a regular character, not a line ending. So "hello\n".chop removes the \n and returns "hello", which is exactly what chomp would do — but chop achieves this by removing any last character, not by recognising the newline. This coincidence makes the two methods look similar on simple examples even though their logic is different.
the bang variant
chop! modifies the receiver in place:
s = "hello"
s.chop!
# => "hell"
s
# => "hell"
# Returns nil on empty string (no modification possible)
"".chop!
# => nil
chop vs chomp
This is the key difference between the two methods: chomp only removes line endings (\n, \r, or \r\n), while chop removes any trailing character regardless of what it is. If the string ends with a letter or digit rather than a newline, chomp leaves it alone but chop still cuts it off.
# chomp targets line endings specifically
"hello\n".chomp
# => "hello"
# chop removes any character
"hello\n".chop
# => "hell"
# chomp on a string with no line ending returns unchanged
"hello".chomp
# => "hello"
# chop always removes one character (or two for CRLF)
"hello".chop
# => "hell"
The examples above highlight the central design difference: chomp is a specialised tool for line endings, while chop is a general-purpose “remove the last character” operation. Choosing between them depends on whether you want to strip only newlines or genuinely want to drop the final character. The next examples show real-world situations where chop is the right tool.
Practical Examples
path manipulation
# Remove a trailing slash from a path
"/usr/local/bin/".chop
# => "/usr/local/bin"
# Remove trailing slashes recursively
path = "/api/v1/users/"
path.chop while path.end_with?("/")
# => "/api/v1/users"
The path manipulation example removes a trailing slash, which is a common need when normalising directory paths or URL segments. The while loop variant keeps chopping as long as the string ends with a slash, handling multiple trailing slashes at once. Without chop, you would need a regex or a custom loop to achieve the same effect.
reading lines the Unix way
Before Ruby had chomp, programmers used chop to clean up lines from files. That older style still appears in legacy code, so it helps to know what it does and why it can be risky:
# Old school line reading (still works)
File.read("data.txt").each_line do |line|
puts line.chop
end
Note that chop is not safe for lines that legitimately end with CRLF — use chomp for that. The each_line iterator already splits on newlines, so the last character of each line is often the newline itself. In that context, chop does the right thing, but the code is fragile if the input format changes.
That is the main tradeoff to keep in mind. chop is simple and direct, but it is only the right tool when you truly want the last character removed, not just a line ending cleaned up.
removing trailing newlines safely
# User input might end with \n or \r\n or neither
input = gets.chop # Always removes the last character
# If you only want to remove line endings:
input = gets.chomp
When reading from gets, the trailing newline is almost always present because the user pressed Enter. Using chomp instead of chop is the safer default here; chop would delete the last typed character if the input happened to lack a newline, which is unlikely with gets but can happen when reading from a file or pipe.
Common Pitfalls
removing more than intended
If a string doesn’t end with a newline, chop still removes a character:
# You meant to remove a newline, but there's none:
"hello".chomp
# => "hello" (safe, no-op)
"hello".chop
# => "hell" (oops, you just removed a letter)
This is the most common mistake with chop: calling it on a string that does not end with a newline. The method still removes one character, which can silently corrupt data. When you are processing text from an unknown source, always check whether the string might not have a trailing newline before reaching for chop.
CRLF double removal
If you combine chomp with chop, you might remove too much:
# Two operations on CRLF line ending:
"hello\r\n".chomp.chop
# => "hel" -- removed both \r\n AND the 'l'!
Chaining chomp and chop together is dangerous because each method operates on the result of the previous one. chomp removes the newline pair, then chop removes the last remaining character — in this case the letter l. If you need both behaviours, apply them separately with an intermediate variable so you can inspect the result at each step.
empty string handling
chop! returns nil on empty strings, which surprises people in conditionals:
# This conditional is wrong:
if str.chop!
# nil is falsy, so this branch won't run even though chop! succeeded
# (unless the string was empty, in which case nil is correct)
end
# Correct:
result = str.chop!
if result
# worked
end
The chop! return value is unusual because it returns nil when no change is possible rather than the unchanged string. This makes chop! useful in conditionals — if str.chop! only enters the branch when a character was actually removed — but it also means you cannot chain chop! with other string methods without checking for nil first.
binary data
chop treats bytes as characters, which causes problems with multi-byte encodings:
# UTF-8 string
"héllo".chop
# => "hell" -- removed the accent, not the 'o'
# For binary strings, use slicing instead
s = "\xfe\xff\x00"
s[0...-1]
# => "\xfe\xff"
See Also
- /reference/string-methods/chomp/ — remove trailing line endings (\n, \r, \r\n)
- /reference/string-methods/chop-bang/ — modify in place, returns nil on empty
- /reference/string-methods/strip/ — remove leading and trailing whitespace