Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a slice with an omitted start and an exclusive end bound one position before the end:

s = "Python"
shorter = s[:-1]
print(shorter)  # Pytho

s[:-1] returns every indexed string element except the final one. It does not modify s; Python strings are immutable, so keep the returned value or assign it back with s = s[:-1].

The direct solution: s[:-1]

Python slice notation is sequence[start:stop]. The start is included, while the stop is excluded. Omitting start means “begin at the first element,” and -1 identifies the position immediately before the final element when used as the exclusive stop. Together, s[:-1] means “from the beginning up to, but not including, the last indexed element.”

name = "Ada Lovelace"
without_last = name[:-1]
print(without_last)  # Ada Lovac

The expression creates a new string value. If you need the variable itself to refer to the shortened value, reassign it:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
s = "Python"
s = s[:-1]
print(s)  # Pytho

The Python Software Foundation’s tutorial describes strings as immutable: their contents cannot be changed in place. A slice therefore returns a result rather than editing the original object.

How the slice bounds work

The built-in types reference defines a slice as the sequence elements whose indexes satisfy i <= k < j. The right-hand bound is exclusive, which is why the character at index -1 is left out.

s = "Python"
print(s[0])    # P
print(s[-1])   # n
print(s[:-1])  # Pytho

Negative indexes count backward from the end. Index -1 is the final element, -2 is the one before it, and so on. Because the stop value is excluded, stopping at -1 retains everything before the final element.

Empty and one-character strings

The same expression safely handles short input:

print(""[:-1])   # ""
print("x"[:-1])  # ""

An empty string has no element to remove, and a one-character string has no elements before its final character. In both cases the result is an empty string; Python adjusts the slice bounds instead of raising an index error.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

This differs from direct indexing. s[-1] raises IndexError for an empty string, whereas s[:-1] simply returns "".

Removing exactly one character versus trimming text

Choose the operation that matches your requirement. These methods are not interchangeable:

Requirement Use Behavior
Remove exactly the final indexed element, whatever it is s[:-1] Returns all elements except the last one.
Remove a known literal suffix s.removesuffix(suffix) Removes that matching suffix string, not an arbitrary final character.
Trim a trailing run made from selected characters s.rstrip(chars) Continues removing characters from the end while they belong to the supplied character set.
Trim trailing whitespace s.rstrip() Removes trailing whitespace, potentially more than one character.

Known suffixes

If the requirement is “remove .csv when it is present,” use the suffix-aware method instead of deleting one character:

filename = "report.csv"
print(filename.removesuffix(".csv"))  # report

This expresses the condition directly: a nonmatching suffix is left unchanged. It solves a different problem from s[:-1].

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Trailing character sets

rstrip(chars) treats its argument as a set of characters and removes a trailing run. For example:

value = "hello!!!"
print(value.rstrip("!"))  # hello

It is unsuitable when exactly one final element must be removed, because several matching characters may be stripped.

Strings are immutable: use or assign the result

This code does not shorten the original object:

s = "Python"
result = s[:-1]
print(s)       # Python
print(result)  # Pytho

Use one of these patterns depending on your intent:

  • Keep both values: assign the slice to a new variable.
  • Replace the variable: write s = s[:-1].
  • Pass it directly: call a function with s[:-1] without storing it.
def label_without_last(label):
    return label[:-1]

print(label_without_last("Draft?"))  # Draft

Unicode and the meaning of “character”

Python indexes a string by string elements (Unicode code points), not necessarily by the visible symbols a person perceives as one character. A displayed symbol can consist of multiple code points, such as a base letter followed by a combining mark or an emoji sequence joined from several code points.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Consequently, s[:-1] removes one indexed code point. It may leave a combining mark, variation selector, or part of a multi-code-point emoji sequence behind, producing a result that looks surprising. The ordinary slice remains correct for code-point-level processing, but it is not a general “remove one visible symbol” operation.

If your requirement is grapheme-cluster-aware editing—removing one user-perceived symbol—use Unicode grapheme segmentation and a library designed for that purpose. The basic slicing rule alone does not provide that behavior.

Practical examples

Removing punctuation added by formatting

line = "Status: OK;"
clean = line[:-1]
print(clean)  # Status: OK

Use this only when the final element is known to be the punctuation you intend to remove. If the text may or may not end in a semicolon, test first or use a suffix operation:

line = "Status: OK;"
if line.endswith(";"):
    line = line.removesuffix(";")

Processing a list of strings

values = ["alpha!", "beta!", "gamma!"]
trimmed = [value[:-1] for value in values]
print(trimmed)  # ['alpha', 'beta', 'gamma']

Each slice returns a new string; the original list elements remain unchanged.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Checking the result in a test

def test_remove_last_character():
    assert "Python"[:-1] == "Pytho"
    assert "x"[:-1] == ""
    assert ""[:-1] == ""

Common mistakes and fixes

Using s[-1] instead of a slice

s[-1] returns only the final element. To return everything except that element, use s[:-1].

Expecting the original string to change

Assign the result back: s = s[:-1]. Without assignment, the original reference still points to the unchanged string.

Calling rstrip() for an exact one-element removal

rstrip() can remove a run of whitespace or selected characters. Replace it with s[:-1] when the requirement is exactly one final indexed element.

Removing a suffix without checking what it is

s[:-1] removes the final element even when it is not the suffix you expected. Use removesuffix("...") when the literal suffix must match.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Unexpected results with emoji or combining marks

Inspect the string’s code points and clarify whether your application needs code-point slicing or grapheme-aware editing. Do not assume one visible symbol always equals one Python index.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and reliability notes

Slicing constructs a new string containing the retained elements. For a one-character removal this is the idiomatic, readable operation. In a loop that repeatedly shortens a very large string, repeatedly copying the remaining text can do unnecessary work; consider collecting pieces and joining them once, or use a data structure suited to repeated edits. For ordinary labels, lines, filenames, and tokens, s[:-1] is the simplest reliable choice.

Because the operation does not mutate shared strings, it is safe to use when the original value is needed elsewhere. Validate the semantic requirement—arbitrary final element, known suffix, trailing character set, or visible Unicode symbol—before choosing the syntax.

Or skip the browser setup

String slicing itself needs no browser or screenshot service. If your developer workflow also needs automated screenshots of documentation, dashboards, or rendered output, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

One GET request returns PNG, JPEG, WebP, or a PDF. The API supports full-page captures with lazy images loaded, CSS-selector element captures, dark mode, device presets, arbitrary viewports, retina scale, PDF paper and page options, custom CSS and JavaScript, pre-capture clicks, hidden selectors, selector/delay/network-idle waits, request and resource blocking, custom headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs.

For AI workflows, its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Using the API from Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Equivalent cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Equivalent Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

See the ScreenshotNeo documentation for parameters and response headers. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Sign up for the free plan.

Decision checklist

  • Need all but the final indexed element? Use s[:-1].
  • Need to remove a specific literal ending? Use removesuffix().
  • Need to trim trailing whitespace or a character set? Use rstrip().
  • Need to remove one visible Unicode symbol? Use grapheme segmentation rather than assuming one index equals one symbol.
  • Need the variable shortened? Reassign the slice result.

Frequently Asked Questions

Can I use a variable instead of a literal string in the slice?

Yes. Any string expression can appear before [:-1], including a function return, dictionary value, or formatted variable, as long as the value supports string slicing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What does a negative step change?

A negative step reverses traversal and is a different operation. For removing only the final element while preserving order, leave the step out and use s[:-1].

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.