To remove the last character from a string in Python, slice up to -1:
text = "Hello World!"
text[:-1] # 'Hello World'
text[:-3] # 'Hello Wor' the last three
Two traps follow from that. Writing text[:-n] with n as a variable empties the string when n is zero, and rstrip removes more characters than you expect.
Both are demonstrated below, on Python 3.12.5.
Removing the last character with slicing
A negative index counts back from the end, so you never need len():
text = "Hello World!"
print("original :", repr(text))
print("[:-1] :", repr(text[:-1]))
print("[:-3] :", repr(text[:-3]), " the last three")
print()
# a negative index counts back from the end, so no len() needed
print("last character is:", repr(text[-1]))
print("without it :", repr(text[:-1]))
Output:
original : 'Hello World!'
[:-1] : 'Hello World'
[:-3] : 'Hello Wor' the last three
last character is: '!'
without it : 'Hello World'
text[:-1] means “everything up to but not including the last character”. text[-1] is that character on its own.
For a fixed number, just write the number: text[:-2] drops the last two, text[:-3] the last three.
Why text[:-n] empties the string when n is zero
This is the bug worth knowing about, because it destroys data rather than raising:
text = "Hello World!"
def remove_last(s, n):
return s[:-n] # looks fine, and it is broken
for n in (3, 2, 1, 0):
print(f"n={n} -> {remove_last(text, n)!r}")
print()
print("n=0 wiped the whole string, because -0 is 0 and s[:0] is empty")
print()
def remove_last_safe(s, n):
return s[:len(s) - n]
print("the safe version:")
for n in (3, 2, 1, 0):
print(f"n={n} -> {remove_last_safe(text, n)!r}")
Output:
n=3 -> 'Hello Wor'
n=2 -> 'Hello Worl'
n=1 -> 'Hello World'
n=0 -> ''
n=0 wiped the whole string, because -0 is 0 and s[:0] is empty
the safe version:
n=3 -> 'Hello Wor'
n=2 -> 'Hello Worl'
n=1 -> 'Hello World'
n=0 -> 'Hello World!'
n=0 returns nothing at all, while n=1 through 3 work correctly.The reason is that -0 and 0 are the same number. text[:-0] is text[:0], which is an empty string.
Any helper written as return s[:-n] carries this bug. It works in every test where n is at least one, then wipes a value in production.
s[:len(s) - n] is the safe form. It behaves correctly for zero and for every positive count.
Why does rstrip remove too many characters?
rstrip looks like the obvious tool and it does something different:
print("rstrip removes EVERY trailing match, not one:")
print(" 'banana'.rstrip('a') ->", repr("banana".rstrip("a")))
print(" 'test...'.rstrip('.') ->", repr("test...".rstrip(".")))
print()
print("slicing and removesuffix remove exactly one:")
print(" 'test...'[:-1] ->", repr("test..."[:-1]))
print(" 'test...'.removesuffix('.') ->", repr("test...".removesuffix(".")))
print()
# and removesuffix does nothing if the string does not end that way
print(" 'hello'.removesuffix('xyz') ->", repr("hello".removesuffix("xyz")), " unchanged")
Output:
rstrip removes EVERY trailing match, not one:
'banana'.rstrip('a') -> 'banan'
'test...'.rstrip('.') -> 'test'
slicing and removesuffix remove exactly one:
'test...'[:-1] -> 'test..'
'test...'.removesuffix('.') -> 'test..'
'hello'.removesuffix('xyz') -> 'hello' unchanged
'test...'.rstrip('.') removes all three dots. The slice removes one.| Method | On 'test...' | Removes |
|---|---|---|
text[:-1] | 'test..' | Exactly one character |
text.rstrip(".") | 'test' | Every trailing dot |
text.removesuffix(".") | 'test..' | One, only if it matches |
removesuffix arrived in Python 3.9 and is the precise tool. It removes one occurrence, and leaves the string alone if it doesn’t end that way.
That safety is the difference. rstrip takes a set of characters, which is the same behaviour that surprises people when removing newlines.
Removing the last character from an empty string
Slicing is safe on an empty string. Indexing is not:
# slicing an empty string is safe, indexing it is not
empty = ""
print('""[:-1] ->', repr(empty[:-1]), " no error")
try:
print(empty[-1])
except IndexError as err:
print('""[-1] ->', type(err).__name__ + ":", err)
print()
print("so text[:-1] needs no length check, but text[-1] does")
Output:
""[:-1] -> '' no error
""[-1] -> IndexError: string index out of range
so text[:-1] needs no length check, but text[-1] does
""[:-1] returns an empty string quietly, so you can call it without checking the length first.
""[-1] raises IndexError: string index out of range. If you need the last character rather than the string without it, check first.
Removing a trailing newline from a Python string
This is the single most common reason people want the last character gone, and slicing is the wrong tool for it:
unix = "value\n"
windows = "value\r\n"
print("unix [:-1] :", repr(unix[:-1]))
print("windows [:-1] :", repr(windows[:-1]), " <- carriage return left behind")
print()
print("unix rstrip :", repr(unix.rstrip()))
print("windows rstrip :", repr(windows.rstrip()), " both handled")
print()
print("use rstrip() for line endings, [:-1] only when you know the text is clean")
Output:
unix [:-1] : 'value'
windows [:-1] : 'value\r' <- carriage return left behind
unix rstrip : 'value'
windows rstrip : 'value' both handled
use rstrip() for line endings, [:-1] only when you know the text is clean
[:-1] on Windows text leaves 'value\r' behind.A Windows line ending is two characters, \r\n. Removing one leaves the carriage return, which is invisible and breaks comparisons.
rstrip() with no argument removes all trailing whitespace including both characters, which is exactly right here.
Removing a trailing comma or slash in Python
The practical cases are nearly always a separator that got added one time too many:
# the classic: a trailing separator built up in a loop
parts = ["apple", "banana", "cherry"]
built = ""
for item in parts:
built += item + ", "
print("built :", repr(built))
print("trimmed :", repr(built[:-2]), " removes the comma AND the space")
print()
# which is why join exists
print("join :", repr(", ".join(parts)), " no trailing separator to remove")
print()
# trailing slash on a path or URL
url = "https://example.com/api/"
print("url :", repr(url.rstrip("/")))
Output:
built : 'apple, banana, cherry, '
trimmed : 'apple, banana, cherry' removes the comma AND the space
join : 'apple, banana, cherry' no trailing separator to remove
url : 'https://example.com/api'
built[:-2] removes the comma and the space together.Count the characters you are removing. A ", " separator is two, so [:-1] leaves a stray space behind.
Better still, don’t create the problem. ", ".join(parts) puts separators only between items, so there is nothing to trim.
For a trailing slash on a URL or path, rstrip("/") is right, because it also handles the case where there are two. It pairs with taking the first n characters.
Common mistakes removing the last character
| Symptom | Cause | Fix |
|---|---|---|
| String came back empty | s[:-n] with n = 0 | Use s[:len(s) - n] |
| Too many characters gone | rstrip strips all matches | Use removesuffix |
| A stray space left | Separator was two characters | Use [:-2] |
| Comparison still fails | Windows \r\n | Use rstrip() |
IndexError | Used text[-1] on empty | Use text[-1:] |
| Nothing changed | Result not assigned | text = text[:-1] |
More Python string trimming guides:
- Remove a character by index
- Remove newlines from a string
- Get the first n characters
- Split the last element of a string
- Reverse a string in Python
- Remove special characters
Frequently asked questions
How do I remove the last character from a string in Python?
Slice it: text[:-1]. Negative indexing is explained in the Python strings tutorial.
How do I remove the last two characters?
text[:-2]. Any fixed count works the same way.
Why does text[:-n] return an empty string?
Because n is zero, and -0 is 0, so the slice becomes text[:0]. Use text[:len(text) - n] instead.
What is the difference between rstrip and removesuffix?
rstrip removes every trailing character in the set you give it. removesuffix removes one occurrence, and only if the string ends with it.
How do I remove a trailing newline?
text.rstrip(). Slicing off one character leaves the carriage return behind in Windows text.
Does removing the last character work on an empty string?
Yes. ""[:-1] returns an empty string with no error, unlike ""[-1].
How do I remove a trailing comma?
text[:-1] for a bare comma, or text[:-2] if the separator was a comma and a space. Using join avoids the problem entirely.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn