Slicing in Python

💡 Big Idea

Slicing is Python's way of extracting a piece of a sequence — a portion of a string, or a section of a list — using a clean, readable notation. Instead of writing a loop to pull out characters one by one, a slice lets you say exactly which part you want in a single expression. Slicing is one of Python's most elegant features: once you understand it, you will use it constantly.

1   Indexing: One Item at a Time

Before slicing makes sense, you need to be comfortable with indexing — accessing a single item from a sequence using its position number. Every character in a string and every item in a list has an index: a number that says where it sits.

🔍 Analogy: Numbered Seats in a Cinema

Imagine a row of seats in a cinema. Each seat has a number. To find someone, you just say their seat number — "seat 3" — and you go directly there without checking every seat. Indexing works the same way: you give Python a position number and it goes straight to that item.

1.1   Zero-Based Indexing

Python counts positions starting from zero, not one. The first item is at index 0, the second at index 1, and so on. This is called zero-based indexing and it is used in almost every programming language.

characterPython
positive index012345
negative index-6-5-4-3-2-1
language = "Python"

print(language[0])    # P  — first character
print(language[1])    # y
print(language[5])    # n  — last character
⚠️ Index out of range

If you ask for an index that does not exist — for example language[10] on a 6-character string — Python raises an IndexError: string index out of range. Valid indices for a string of length n run from 0 to n-1.

1.2   Negative Indices: Counting From the End

Python also lets you count backwards from the end using negative numbers. Index -1 is always the last item, -2 is second-to-last, and so on. This is incredibly useful when you want the end of a sequence but you do not know how long it is.

language = "Python"

print(language[-1])   # n  — last character
print(language[-2])   # o  — second to last
print(language[-6])   # P  — same as [0]
🔍 Why negative indices are useful

Imagine you have a filename like "report_2026_final.csv" and you want the last four characters (the extension). You could count the characters and use a specific index — but what if the filename changes length? With negative indexing, filename[-4:] always gives you the last four characters, no matter how long the name is. You will see this pattern everywhere.

✅ Check Your Understanding
  1. What index does Python use for the first item in a sequence? Why does it start there instead of 1?
  2. Given word = "banana", what is the value of word[2]? What about word[-1]?
  3. A string has 8 characters. What are the valid positive indices? What is the index of the last character?
  4. Without running the code, what does "hello"[-3] return? Explain how you worked it out.

2   What Is a Slice?

An index gives you one item. A slice gives you a range of items — a section of the sequence from one position to another, returned as a new string or list.

🔍 Analogy: Cutting a Baguette

A whole baguette is your sequence. You can pick up one piece (indexing), or you can cut out a section — "from the third mark to the seventh mark" — and lift out that portion (slicing). The original baguette is unchanged; you have just taken a piece. Python slices work exactly this way: the original string or list is never modified; a new, smaller copy is created.

2.1   Basic Slice Notation

A slice is written with square brackets and a colon separating two positions:

sequence[start : stop]
  • start — the index of the first item to include.
  • stop — the index of the first item not to include. The slice stops before this position.
language = "Python"
#            0 1 2 3 4 5

print(language[0:3])    # Pyt   — indices 0, 1, 2  (stops before 3)
print(language[2:5])    # tho   — indices 2, 3, 4  (stops before 5)
print(language[1:4])    # yth   — indices 1, 2, 3  (stops before 4)
🟠 Common Confusion: the stop index is not included

language[0:3] gives you indices 0, 1, and 2 — not index 3. The stop value is a boundary marker, not the last item to include. Think of it as: "start at start, keep going, stop when you reach stop." You never pick up the item at stop itself.

This is consistent with how range(start, stop) works — and for the same reason. Once you expect it, it feels natural. Until then, it is the single most common slicing mistake.

2.2   Visualising the Slice

A helpful mental model: think of the indices as sitting between the characters, like fence posts between fence panels. A slice cuts between two fence posts and lifts out everything between them.

characterPython
cut positions →0123456

[0:3] cuts at position 0 and position 3, lifting out Pyt. [2:5] cuts at position 2 and position 5, lifting out tho. Position 6 is one past the end — that is why [0:6] gives you the entire string.

3   Shorthand: Omitting start or stop

Python lets you leave out either — or both — values in a slice. When you omit a value, Python uses a sensible default:

NotationMeaningExample ("Python")Result
[ : stop]From the very beginning up to (not including) stop."Python"[:3]"Pyt"
[start : ]From start all the way to the end."Python"[2:]"thon"
[ : ]The entire sequence — a full copy."Python"[:]"Python"
word = "Python"

print(word[:3])     # Pyt   — first 3 characters
print(word[3:])     # hon   — everything from index 3 onward
print(word[:])      # Python — a full copy
💜 Omitting start and stop — why it's useful

word[:3] is cleaner and more readable than word[0:3]. When start is 0 or stop is the end of the sequence, omitting that value signals your intent clearly: "I want everything from the beginning" or "I want everything to the end." You will see this shorthand constantly in real Python code.

✅ Check Your Understanding
  1. Given text = "abcdefgh", predict the result of each slice:

    text[2:5]
    text[:4]
    text[5:]
    text[:]
  2. Why does "Python"[0:3] return "Pyt" and not "Pyth"?
  3. Without running it, what does "hello world"[6:] return?
  4. A student wants the first 5 characters of a string called title. Write two different slice expressions that both produce the same result.

4   Negative Indices in Slices

Just like with single-item indexing, you can use negative numbers in slices to count from the end. This is especially powerful when you do not know the length of the sequence in advance.

filename = "report_2026_final.csv"

print(filename[-4:])      # .csv  — the last 4 characters (the extension)
print(filename[:-4])      # report_2026_final  — everything except the extension
print(filename[-8:-4])    # inal  — 4 characters, 8 from the end to 4 from the end
🔍 Analogy: Measuring from both ends of a ruler

A ruler lets you measure from the left (positive indices) or from the right (negative indices). A slice can mix both: start from a position on the left, stop at a position measured from the right. The cut is still just two positions on the same ruler — one from each end.

phrase = "Hello, World!"
#         0123456789...

print(phrase[-6:])       # orld!  — last 6 characters
print(phrase[:-1])       # Hello, World  — everything except the final !
print(phrase[-6:-1])     # orld   — 5 characters near the end
✅ Check Your Understanding
  1. Given email = "[email protected]", write a slice that extracts just "school.edu" using a negative start index. How many characters from the end does it start?
  2. What does "abcdefgh"[-3:] return? What about "abcdefgh"[:-3]?
  3. A filename always ends with a 4-character extension including the dot (e.g. .txt, .csv, .jpg). Write a slice that extracts just the name (everything before the extension) for any filename.
  4. Predict the output:

    s = "programming"
    print(s[-4:])
    print(s[:-7])
    print(s[-7:-4])

5   The Third Value: Step

The full slice notation actually has a third optional value — the step. It tells Python how many positions to jump between each item it picks up:

sequence[start : stop : step]

When you write [0:6], the step defaults to 1 — pick up every item. With [0:6:2], the step is 2 — pick up every other item.

5.1   Step in Action

letters = "abcdefgh"

print(letters[::2])      # aceg  — every other character (step 2)
print(letters[1::2])     # bdfh  — starting from index 1, every other
print(letters[::3])      # adg   — every third character
print(letters[0:8:2])    # aceg  — explicit start/stop with step 2

5.2   Reversing with Step -1

The most famous use of the step value is reversing a sequence. A step of -1 means "go backwards one position at a time." Combined with omitting start and stop, it reverses the entire sequence:

word = "Python"
print(word[::-1])        # nohtyP — the word reversed

numbers = [1, 2, 3, 4, 5]
print(numbers[::-1])     # [5, 4, 3, 2, 1]

[::-1] is a Python idiom — a well-known, widely used shorthand that every Python programmer recognises immediately. It is worth memorising.

🔍 How [::-1] works

When step is negative, Python starts from the end and works backwards. Omitting start defaults to "the last position" and omitting stop defaults to "before the beginning." So [::-1] means: "start at the end, stop at the beginning (exclusive), step backwards by 1 each time" — which produces the sequence in reverse.

⚠️ Step 0 is not allowed

A step of 0 would mean "don't move" — Python would be stuck in place forever. If you write [::0], Python raises a ValueError: slice step cannot be zero. Any non-zero integer is valid as a step.

✅ Check Your Understanding
  1. What does the step value in a slice control?
  2. Predict the output of each of these:

    s = "abcdefghij"
    print(s[::2])
    print(s[1::2])
    print(s[::-1])
    print(s[8🔢-1])
  3. Write a single slice expression that reverses the string "racecar". What do you notice about the result?
  4. A student wants every third item from a list starting at index 0. Write the slice.
  5. True or False: [::-1] modifies the original string. Explain your answer.

6   Slicing Lists

Everything you have learned about slicing strings applies equally to lists. The notation is identical — the only difference is that you get back a list instead of a string.

scores = [85, 42, 91, 67, 55, 78, 39, 95]
#          0   1   2   3   4   5   6   7

print(scores[0:3])      # [85, 42, 91]  — first three scores
print(scores[3:])       # [67, 55, 78, 39, 95]  — from index 3 onward
print(scores[:3])       # [85, 42, 91]  — same as [0:3]
print(scores[-3:])      # [78, 39, 95]  — last three scores
print(scores[::2])      # [85, 91, 55, 39]  — every other score
print(scores[::-1])     # [95, 39, 78, 55, 67, 91, 42, 85]  — reversed

6.1   Slices Create New Lists — The Original Is Unchanged

A slice never modifies the original. It creates a brand-new sequence containing the selected items. This means you can slice freely without worrying about damaging your data:

scores = [85, 42, 91, 67, 55]

top_three = scores[:3]        # a new list — [85, 42, 91]
bottom_two = scores[-2:]      # a new list — [67, 55]

print(scores)                 # [85, 42, 91, 67, 55] — unchanged
print(top_three)              # [85, 42, 91]
print(bottom_two)             # [67, 55]

6.2   Practical List Slicing

students = ["Alice", "Bob", "Carol", "Dan", "Eve", "Fran"]

# First half vs second half
midpoint = len(students) // 2
group_a = students[:midpoint]
group_b = students[midpoint:]
print("Group A:", group_a)     # ['Alice', 'Bob', 'Carol']
print("Group B:", group_b)     # ['Dan', 'Eve', 'Fran']

# Skip the first and last (sometimes used to ignore header/footer rows)
middle = students[1:-1]
print("Middle:", middle)       # ['Bob', 'Carol', 'Dan', 'Eve']
✅ Check Your Understanding
  1. Given nums = [10, 20, 30, 40, 50, 60, 70, 80], predict the result of each slice:

    nums[2:5]
    nums[:4]
    nums[-3:]
    nums[::3]
    nums[::-1]
  2. After this code runs, what are the values of original, first_half, and second_half?

    original = [1, 2, 3, 4, 5, 6]
    first_half = original[:3]
    second_half = original[3:]
    first_half[0] = 99
    print(original)

    Does modifying first_half affect original? Why or why not?

  3. Write a slice that extracts the last 4 items from any list called data, regardless of how long it is.
  4. You have a list of 100 exam results. Write a slice that gives you every fifth result, starting from the first.

7   An Important Difference: Strings vs. Lists

Slicing behaves identically on strings and lists — with one crucial difference: strings are immutable. You cannot change a character inside a string. Lists are mutable — you can change, add, and remove items.

# Strings — immutable, cannot change individual characters
name = "Python"
name[0] = "J"        # TypeError: 'str' object does not support item assignment

# To "change" a string, you must build a new one using slices:
name = "J" + name[1:]
print(name)          # Jython
# Lists — mutable, individual items can be changed directly
colours = ["red", "green", "blue"]
colours[1] = "yellow"
print(colours)       # ['red', 'yellow', 'blue']

# You can also replace a slice of a list:
colours[0:2] = ["pink", "purple"]
print(colours)       # ['pink', 'purple', 'blue']
🟠 Common Confusion: "Why can't I change a character in my string?"

Strings in Python are immutable — once created, their characters cannot be changed. Attempting to do so raises a TypeError. This is not a bug; it is a deliberate design choice that makes strings safe to share across a program without unexpected side effects.

If you need to modify a string, the pattern is: use slicing to take the parts you want, combine them with the new content using +, and assign the result to the variable. The "new" string is actually a brand-new object — the old one is untouched.

✅ Check Your Understanding
  1. What does "immutable" mean? Which type is immutable — strings or lists?
  2. This code raises an error. Why? How do you fix it to produce "Hello, World!"?

    greeting = "hello, World!"
    greeting[0] = "H"
    print(greeting)
  3. Given items = ["a", "b", "c", "d", "e"], what does items[1:4] = ["X", "Y"] do to the list? Predict the result, then verify.

8   Common Slice Patterns Worth Memorising

These patterns come up so frequently in real Python code that they are worth learning by name, just like the loop patterns in Article 5.

PatternSliceWhat it doesExample
First N items[:n]The first n items."Python"[:3]"Pyt"
Last N items[-n:]The last n items."Python"[-3:]"hon"
Everything except first N[n:]Skip the first n items."Python"[2:]"thon"
Everything except last N[:-n]Skip the last n items."Python"[:-2]"Pyth"
Reverse[::-1]The entire sequence, backwards."Python"[::-1]"nohtyP"
Every other item[::2]Items at even indices (0, 2, 4…)"Python"[::2]"Pto"
Full copy[:]A new copy of the entire sequence.scores[:] → copy of scores
File extension[-4:]Last 4 characters — assumes a 3-char extension + dot."data.csv"[-4:]".csv"

9   Putting It All Together

Here is a program that uses slicing in several realistic ways. Read it and predict the output before running it.

def is_palindrome(word):
    """Returns True if word reads the same forwards and backwards."""
    return word.lower() == word.lower()[::-1]

def file_extension(filename):
    """Returns the file extension including the dot."""
    dot_index = filename.rfind(".")         # find the last dot
    return filename[dot_index:]

def truncate(text, max_length):
    """Shortens text to max_length characters, adding '...' if needed."""
    if len(text) <= max_length:
        return text
    return text[:max_length] + "..."

def initials(full_name):
    """Returns initials from a full name, e.g. 'Ada Lovelace' -> 'A.L.'"""
    parts = full_name.split()               # split into a list of words
    return ".".join(word[0] for word in parts) + "."


# ── Testing each function ─────────────────────────────────────────
test_words = ["racecar", "python", "level", "hello"]
for word in test_words:
    result = "✓ palindrome" if is_palindrome(word) else "✗ not a palindrome"
    print(f"  {word:<10} {result}")

print()
filenames = ["report.docx", "data_2026.csv", "image.final.png"]
for f in filenames:
    print(f"  {f:<25} extension: {file_extension(f)}")

print()
long_text = "The quick brown fox jumped over the lazy dog"
print(truncate(long_text, 20))
print(truncate("Short text", 20))

print()
names = ["Ada Lovelace", "Alan Turing", "Grace Hopper"]
for name in names:
    print(f"  {name:<20} initials: {initials(name)}")

Slicing at work in this program:

  • word.lower()[::-1] — reverses the lowercased word to check for palindromes.
  • filename[dot_index:] — slices from the dot to the end to get the extension.
  • text[:max_length] — takes the first max_length characters for truncation.
  • word[0] — single-character index to get each word's first letter for initials.

10   Final Check: The Big Picture

💡 Big Idea

Slicing extracts a portion of a string or list using the notation [start : stop : step]. The stop index is never included. Omitting start defaults to the beginning; omitting stop defaults to the end. Negative indices count from the end. A step of -1 reverses a sequence. Slices never modify the original — they always return a new copy. Strings are immutable and cannot be changed in place; lists are mutable and can be. Knowing the common slice patterns by name makes your code shorter, cleaner, and more readable.

✅ Final Check Your Understanding
  1. Given s = "abcdefghij" (10 characters), predict the result of every expression in this table before running anything:

    ExpressionYour prediction
    s[3] 
    s[-2] 
    s[2:6] 
    s[:4] 
    s[7:] 
    s[-4:] 
    s[:-3] 
    s[::2] 
    s[::-1] 
    s[1:8:3] 
  2. A student writes word = "elephant" and wants the middle four characters ("pha"... wait, let them figure it out). What slice gives you characters at positions 2, 3, 4, and 5? Write it two different ways — once with positive indices, once with negative.
  3. This code is supposed to check whether a string is a palindrome, but it has a bug. Find it and fix it.

    def is_palindrome(s):
        return s == s[-1👎-1]
    
    print(is_palindrome("racecar"))   # should be True
    print(is_palindrome("hello"))     # should be False
  4. Write a function called middle(sequence) that returns everything except the first and last items of any string or list. For example, middle("Python") should return "ytho", and middle([1,2,3,4,5]) should return [2,3,4].
  5. Explain in plain English why this does not work, and how to fix it:

    name = "alice"
    name[0] = "A"
    print(name)

11   Quick-Reference Summary

NotationWhat it meansExample → Result
s[i]Single item at index i."Python"[1]"y"
s[-i]Single item, i positions from the end."Python"[-1]"n"
s[start:stop]Items from start up to (not including) stop."Python"[1:4]"yth"
s[:stop]From the beginning up to (not including) stop."Python"[:3]"Pyt"
s[start:]From start to the end."Python"[3:]"hon"
s[:]Full copy of the sequence."Python"[:]"Python"
s[start:stop:step]Every step-th item from start to stop."Python"[::2]"Pto"
s[::-1]The entire sequence reversed."Python"[::-1]"nohtyP"
s[-n:]The last n items."Python"[-3:]"hon"
s[:-n]Everything except the last n items."Python"[:-2]"Pyth"