Python String Indexing & Slicing: Access and Cut Text
Python indexing gets one character from a string by position, and slicing gets a range of characters. Indexes start at 0, negative indexes count from the end, and slices use the form string[start:stop:step] to build a new string without changing the original. This article shows how the syntax works, how Python counts positions, and the mistakes that cause confusing results.
Difficulty: Beginner
Helpful to know first: basic Python variables, strings as text values, and how zero-based counting works.
How Python String Indexing and Slicing Work
A Python string is an ordered sequence of characters. Indexing means picking one character by its position, and slicing means taking a smaller string made from a range of positions.
Use indexing when you want one character, such as the first letter of a name. Use slicing when you want several characters, such as a prefix, a suffix, or a substring.
Python uses zero-based indexing. That means the first character is at index 0, the second character is at index 1, and so on.
word = "Python"
print(word[0])
print(word[1])
print(word[-1])
# Output:
# P
# y
# nIn that example, word[0] returns the first character, word[1] returns the second, and word[-1] returns the last character. Negative indexes count backward from the end of the string.
Indexing a String One Character at a Time
Indexing is the simplest way to read a single character. If the index is valid, Python returns a one-character string. If it is too large or too small, Python raises an error.
Reading the first, middle, and last characters
When you know the position you want, indexing is direct and clear. This is common when you read file names, check prefixes, or look at fixed-format text.
text = "camera"
print(text[0])
print(text[3])
print(text[-1])
# Output:
# c
# e
# aThe string "camera" has six characters. Index 0 is c, index 3 is e, and index -1 is the last character, a.
Why negative indexes are useful
Negative indexes are helpful when you care about the end of the string more than the beginning. For example, the last letter of a word or the final extension in a file name is often easier to reach with -1 than by counting the full length.
filename = "notes.txt"
print(filename[-1])
print(filename[-4])
# Output:
# t
# .The last character is t. Four characters from the end is the dot before the extension. Negative indexes still follow the same rule: -1 is the last character, -2 is the character before that, and so on.
Slicing a String to Get a Range of Characters
A slice extracts a part of the string and returns a new string. The basic form is string[start:stop]. The start position is included, but the stop position is not.
That last detail is the part many beginners miss. If you write text[1:4], Python returns positions 1, 2, and 3, but not 4.
The start is included and the stop is excluded
Think of a slice like cutting between positions, not on positions. The left edge belongs to the slice, and the right edge does not.
word = "Python"
print(word[1:4])
print(word[:2])
print(word[4:])
# Output:
# yth
# Py
# onword[1:4] gives the letters from index 1 through 3. word[:2] means “from the start up to, but not including, index 2.” word[4:] means “from index 4 to the end.”
Getting prefixes, suffixes, and middle parts
Slicing is the standard way to get common pieces of a string without writing loops. Prefixes, suffixes, and middle sections all use the same syntax.
email = "[email protected]"
print(email[:7])
print(email[8:])
print(email[8:15])
# Output:
# student
# example.com
# exampleThe first slice gets the username prefix. The second slice gets the domain name. The third slice gets only the middle part you asked for. Python does not change the original string in any of these cases.
Using Step Values to Skip Characters or Reverse Text
The full slice form is string[start:stop:step]. The step tells Python how far to move between characters. A step of 2 skips every other character. A step of -1 walks backward through the string.
Skipping every other character
Positive steps are useful when you want regular spacing, such as every second letter or every third character in a code string.
letters = "abcdefg"
print(letters[::2])
print(letters[1::2])
# Output:
# aceg
# bdfThe first slice starts at the beginning and takes every second character. The second slice starts at index 1, then takes every second character from there.
Reversing a string with a negative step
When the step is negative, Python moves from right to left. The most common example is reversing a string with [::-1].
word = "straw"
print(word[::-1])
print(word[-1:-6:-1])
# Output:
# warts
# wartsThe first slice reverses the whole string. The second slice does the same thing, but it shows how negative start, stop, and step values can work together.
How Slice Boundaries Behave in Detail
Slice boundaries follow rules that are easy to miss if you only memorize the syntax. Understanding these rules helps you avoid off-by-one errors and makes the results predictable.
Out-of-range slice limits do not cause an error
Unlike indexing, slicing is forgiving. If a slice start or stop is outside the string, Python trims the request to the available characters instead of raising an error.
name = "Ada"
print(name[0:10])
print(name[-10:2])
# Output:
# Ada
# AdThis is one reason slices are safer than index access when the exact length is not known. Python quietly uses the part that exists.
A slice always creates a new string
Strings in Python are immutable, which means you cannot change a string in place. A slice returns a new string object with the selected characters.
original = "hello"
part = original[1:4]
print(original)
print(part)
# Output:
# hello
# ellThe original string stays the same. The slice is a separate value, even though it was built from the original text.
Indexing and slicing follow the same character positions
If you can count the positions for indexing, you can use the same positions for slicing. For example, if index 3 is the character you want to include, then the slice stop must be 4 to include it.
text = "abcdef"
print(text[2])
print(text[2:3])
# Output:
# c
# cThat relationship is useful because it helps you move between “one character” and “a range of characters” without changing your counting method.
Predict the Output
These short examples test the most common misunderstandings: whether slices include the stop index and what negative steps do.
Predict the result of a slice with a stop index
What does this print?
text = "planet"
print(text[1:4])Show answer
It prints lan. The slice starts at index 1 and stops before index 4, so it includes l, a, and n.
# text = "planet"
# indices: 0 1 2 3 4 5
# letters: p l a n e t
# text[1:4] -> "lan"Predict the result of a reverse slice
What does this print?
text = "abcde"
print(text[::-1])Show answer
It prints edcba. A step of -1 tells Python to move backward one character at a time, starting from the end of the string.
# text = "abcde"
# text[::-1] -> "edcba"Common Mistakes With String Indexing and Slicing
Using the last index instead of one past the end
Beginners often expect the stop value in a slice to work like an inclusive endpoint. In Python, the stop value is excluded, so you need one more than the last index you want.
Problem: This slice misses the final character because the stop index is not included.
word = "Python"
print(word[0:5])
# Output: PythoFix: Use one past the last character you want, or leave the stop blank when you want the rest of the string.
word = "Python"
print(word[0:6])
print(word[:])
# Output:
# Python
# PythonThe corrected version works because the stop index is now far enough to include the final character.
Using an index that is outside the string
Indexing is stricter than slicing. If you ask for a character that does not exist, Python raises an error instead of trimming the request.
Problem: This code asks for character 4 in a three-character string, so Python cannot return a result.
name = "Ada"
print(name[3])
# IndexError: string index out of rangeFix: Use a valid index, or use slicing if you want Python to handle ranges that may be shorter than expected.
name = "Ada"
print(name[2])
print(name[2:10])
# Output:
# a
# aThe corrected version works because index 2 is valid, and the slice safely stops at the end of the string.
Forgetting that a slice with a negative step needs matching bounds
When the step is negative, the start and stop positions move in the opposite direction from normal slices. If you keep them in the same order, the slice can come back empty.
Problem: This slice looks like it should reverse part of the string, but the boundaries move the wrong way for a negative step.
text = "abcdef"
print(text[1:4:-1])
# Output: Fix: Put the larger index first when stepping backward.
text = "abcdef"
print(text[4:1:-1])
# Output:
# edcThe corrected version works because the slice starts later in the string and moves backward toward the stop position.
Find the Bug
One of the slices below does not do what the author wanted. Find the bug and fix it.
code = "python"
first_three = code[0:3]
last_three = code[-3:]
print(first_three, last_three)
# Expected output: pyt honShow answer
The bug is in the expected result, not the slice syntax. The code actually prints pyt hon only if there is a space between the values when print joins them. The slices themselves are correct.
The real issue is that the expected output may confuse the two substrings with one continuous word. If the goal is to show the pieces separately, the code is fine. If the goal is to create one string, you need to join them.
code = "python"
first_three = code[0:3]
last_three = code[-3:]
print(first_three + last_three)
# Output: pythonThe fixed version works because string concatenation combines the two slices into one value.
When to Use Indexing, Slicing, or Something Else
Indexing and slicing are best when you already know the positions you need. If you need to search for a character, split by a separator, or remove whitespace, another string method may be a better fit.
| Task | Best choice | Why |
|---|---|---|
| Get one known character | Indexing | Direct, simple, and readable |
| Get a prefix, suffix, or substring | Slicing | Flexible and concise |
| Find text by a delimiter | split() | Works by content, not position |
| Trim whitespace | strip() | Designed for leading and trailing spaces |
| Check whether a string starts or ends with something | startswith() or endswith() | Clearer than manual index checks |
Use slicing when the position matters. Use string methods when the content matters more than the position.
Interview Questions About String Indexing and Slicing
Why does Python use zero-based indexing?
Zero-based indexing makes the first position 0, which matches how slices are counted from a starting point. It also fits the way Python and many other languages work with ranges and offsets.
What is the difference between s[0] and s[:1]?
They usually return the same text when the string is not empty, but s[0] returns one character and can raise IndexError if the string is empty. s[:1] always returns a string and gives an empty string for empty input.
Why does slicing not change the original string?
Strings are immutable in Python. A slice builds a new string value, so the original text stays unchanged.
Try It Yourself
Write a program that takes the string "programming" and prints:
- the first four characters
- the last four characters
- the string reversed
Expected output:
prog
ming
gnimmargorpHint: use one normal slice, one negative slice, and one reverse slice.
Show solution
text = "programming"
print(text[:4])
print(text[-4:])
print(text[::-1])
# Output:
# prog
# ming
# gnimmargorpSummary
- Indexing gets one character with string[index].
- Slicing gets a range with string[start:stop:step].
- The start of a slice is included, but the stop is excluded.
- Negative indexes count from the end of the string.
- Slices do not raise IndexError when the limits go past the string length.
- Strings are immutable, so slicing returns a new string instead of changing the original.
Next, learn how Python string methods such as split(), strip(), and replace() work with the same text values.