Slicing Lists & Strings

Reviewed & published by Brayan K

Slicing is the syntax seq[start:stop:step] for extracting a new sub-sequence — a range of elements from a list, string, or tuple — without writing a loop.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Once you can grab "the first three", "everything after the second", or "this reversed" in a single bracket expression, you'll reach for slicing constantly. It's one of the most distinctly Pythonic tools you'll learn.

What You'll Learn in This Lesson

1 start : stop — Grabbing a Range

A slice asks for everything from start up to but not including stop. The same syntax works on lists and strings:

letters = ["a", "b", "c", "d", "e", "f"]
#           0    1    2    3    4    5

print(letters[1:4])      # ['b', 'c', 'd']  -> indexes 1, 2, 3
print(letters[0:2])      # ['a', 'b']       -> stop (2) excluded

text = "Python"
print(text[0:3])         # 'Pyt'
print(text[2:5])         # 'tho'

📐 How to read the indexes:

 a   b   c   d   e   f
 0   1   2   3   4   5     <- start counts here
-6  -5  -4  -3  -2  -1     <- negatives count from the end

Think of indexes as sitting between the items. letters[1:4] takes the three elements between position 1 and position 4.

# Basic start:stop slicing on a list and a string
nums = [10, 20, 30, 40, 50, 60, 70]
word = "slicing"

# First three numbers
print(nums[0:3])     # [10, 20, 30]

# The middle chunk
print(nums[2:5])     # [30, 40, 50]

# A piece of the word
print(word[1:4])     # 'lic'

# stop past the end just clamps - no error
print(nums[5:100])   # [60, 70]

# ✅ Expected output:
# [10, 20, 30]
# [30, 40, 50]
# lic
# [60, 70]

✂️ Omitting Bounds & Negative Indices

Leave out start to mean "from the beginning" and leave out stop to mean "to the end". Negative numbers count back from the last item:

data = [10, 20, 30, 40, 50]

print(data[:3])      # [10, 20, 30]   -> start defaults to 0
print(data[2:])      # [30, 40, 50]   -> stop defaults to the end
print(data[:])       # [10, 20, 30, 40, 50]  -> a full copy

# Negative indices count from the end
print(data[-1])      # 50   -> last item
print(data[-2:])     # [40, 50]  -> last two
print(data[:-1])     # [10, 20, 30, 40]  -> everything but the last

# Mix them freely
print(data[1:-1])    # [20, 30, 40]  -> drop first AND last

data[:-1] ("all but the last") and data[-2:] ("the last two") are everyday idioms — worth memorizing.

# Omitting bounds and using negative indices
path = "report_2024.csv"

# Drop the last 4 characters (the ".csv")
print(path[:-4])       # 'report_2024'

# Keep only the extension
print(path[-3:])       # 'csv'

scores = [88, 92, 75, 60, 95, 70]
print("First two:", scores[:2])    # [88, 92]
print("Last two:", scores[-2:])    # [95, 70]
print("Middle:", scores[1:-1])     # [92, 75, 60, 95]

# ✅ Expected output:
# report_2024
# csv
# First two: [88, 92]
# Last two: [95, 70]
# Middle: [92, 75, 60, 95]

2 The step — Every Nth Item & Reversing

The third number is the step: how far to jump each time. A step of 2 takes every other item; a negative step walks backwards:

nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(nums[::2])     # [0, 2, 4, 6, 8]   -> every 2nd item
print(nums[1::2])    # [1, 3, 5, 7, 9]   -> odds, starting at 1
print(nums[::3])     # [0, 3, 6, 9]      -> every 3rd item

# A negative step reverses
print(nums[::-1])    # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

text = "racecar"
print(text[::-1])    # 'racecar'  (a palindrome!)
print("hello"[::-1]) # 'olleh'
# step controls the stride; -1 reverses
digits = [1, 2, 3, 4, 5, 6, 7, 8]

print("Every 2nd:", digits[::2])    # [1, 3, 5, 7]
print("Every 3rd:", digits[::3])    # [1, 4, 7]
print("Reversed:", digits[::-1])    # [8, 7, 6, 5, 4, 3, 2, 1]

# A quick palindrome check using a reversed slice
def is_palindrome(s):
    return s == s[::-1]

print(is_palindrome("level"))   # True
print(is_palindrome("python"))  # False

# ✅ Expected output:
# Every 2nd: [1, 3, 5, 7]
# Every 3rd: [1, 4, 7]
# Reversed: [8, 7, 6, 5, 4, 3, 2, 1]
# True
# False

🧩 Slice Copies & Slice Assignment

Reading a slice makes a copy

A slice never touches the original — seq[:] is the classic shallow-copy trick:

original = [1, 2, 3]
copy = original[:]      # a separate list with the same items
copy.append(4)
print(original)         # [1, 2, 3]   -> untouched
print(copy)             # [1, 2, 3, 4]

Assigning TO a slice edits in place

Put a slice on the left of = to replace that whole section — the replacement can even be a different length:

nums = [10, 20, 30, 40, 50]

nums[1:3] = [99, 99]        # replace indexes 1-2
print(nums)                 # [10, 99, 99, 40, 50]

nums[1:3] = [0]             # shrink (2 items -> 1)
print(nums)                 # [10, 0, 40, 50]

nums[1:1] = [7, 8]          # insert without removing
print(nums)                 # [10, 7, 8, 0, 40, 50]

🎯 Your Turn: Fill in the Blanks

Replace each ___ so the slices produce the commented output. Think about which bound or step you need.

# 🎯 YOUR TURN: replace each ___ to match the expected output
seq = [10, 20, 30, 40, 50, 60]

# 1. The first three items -> [10, 20, 30]
print(seq[___])

# 2. The last two items -> [50, 60]
print(seq[___])

# 3. The whole list reversed -> [60, 50, 40, 30, 20, 10]
print(seq[___])

# 4. Every other item -> [10, 30, 50]
print(seq[___])

# Expected output:
# [10, 20, 30]
# [50, 60]
# [60, 50, 40, 30, 20, 10]
# [10, 30, 50]
print(seq[:3])      # [10, 20, 30]
print(seq[-2:])     # [50, 60]
print(seq[::-1])    # [60, 50, 40, 30, 20, 10]
print(seq[::2])     # [10, 30, 50]

Common Errors & How to Fix Them

❌ Expecting the stop index to be included

"abcd"[0:2]   # you might expect 'abc' ... it's 'ab'

✅ The stop is excluded. To include index 2, use [0:3]. Remember: stop = "first index NOT taken".

❌ Mixing up index errors and slice clamping

nums = [1, 2, 3]
nums[10]      # IndexError!
nums[1:10]    # fine -> [2, 3]

✅ A single index out of range raises IndexError; a slice out of range simply clamps to what exists.

❌ Reversing with a positive step

[1, 2, 3][3:0]    # [] (empty) - going backwards needs a -step

✅ To go right-to-left you need a negative step: [1, 2, 3][::-1] gives [3, 2, 1].

🎯 Mini Challenge: Filename Surgery

Use only slicing to pull apart a filename and a CSV row. No split() allowed!

# Mini Challenge: slice a filename and a fixed-width record
filename = "invoice_0042.pdf"

# 1. The base name without extension -> 'invoice_0042'
base = filename[:-4]
print("Base:", base)

# 2. The extension without the dot -> 'pdf'
ext = filename[-3:]
print("Ext:", ext)

# 3. Just the 4-digit number -> '0042'
number = filename[8:12]
print("Number:", number)

# 4. The base, reversed -> '2400_eciovni'
print("Reversed:", base[::-1])

# Expected output:
# Base: invoice_0042
# Ext: pdf
# Number: 0042
# Reversed: 2400_eciovni

📚 Keep Learning

Go deeper with the official Python documentation:

Frequently Asked Questions

Lesson complete — you slice like a pro!

You can grab ranges with start:stop, count from the end with negatives, stride or reverse with step, copy with [:], and rewrite sections with slice assignment. These tricks return again and again in real code.

Practice quiz

What is the full slice syntax?

  • seq[start, stop, step]
  • seq(start:stop:step)
  • seq[start:stop:step]
  • seq{start:stop:step}

Answer: seq[start:stop:step]. Slicing uses square brackets with colons: seq[start:stop:step].

Given letters = ['a','b','c','d','e','f'], what is letters[1:4]?

  • ['b','c','d']
  • ['a','b','c','d']
  • ['b','c','d','e']
  • ['a','b','c']

Answer: ['b','c','d']. Indexes 1, 2, 3 are taken; stop (4) is excluded, giving ['b','c','d'].

Why is the stop index excluded in a slice?

  • It's a bug in Python
  • Stop is always included
  • Only for strings
  • Slices are half-open: stop is the first index NOT included

Answer: Slices are half-open: stop is the first index NOT included. Slices are half-open, so the length is stop minus start and seq[:i]+seq[i:] rebuilds the original.

What does 'Python'[2:5] return?

  • 'yth'
  • 'tho'
  • 'thon'
  • 'ytho'

Answer: 'tho'. Indexes 2,3,4 of 'Python' are t, h, o, giving 'tho'.

What does data[:3] mean?

  • From the start up to (not including) index 3
  • From index 3 to the end
  • Every 3rd item
  • The last 3 items

Answer: From the start up to (not including) index 3. Omitting start defaults to 0, so data[:3] takes the first three items.

What does data[-2:] return?

  • Everything but the last two
  • The second item
  • The last two items
  • An empty list

Answer: The last two items. Negative indices count from the end; -2: gives the last two items.

What does seq[::-1] do?

  • Removes the last item
  • Returns a reversed copy of the sequence
  • Returns every other item
  • Raises an error

Answer: Returns a reversed copy of the sequence. A step of -1 walks backwards over the whole sequence, returning a reversed copy.

What does nums[::2] return for [0,1,2,3,4,5]?

  • [1,3,5]
  • [0,1,2]
  • [2,4]
  • [0,2,4]

Answer: [0,2,4]. A step of 2 starting at 0 takes every other item: [0,2,4].

What is seq[:] commonly used for?

  • Deleting a list
  • Making a shallow copy of the sequence
  • Reversing a list
  • Sorting

Answer: Making a shallow copy of the sequence. seq[:] produces a brand-new copy and leaves the original untouched.

What happens with nums[10] vs nums[1:10] on [1,2,3]?

  • Both raise IndexError
  • Both clamp to what exists
  • nums[10] raises IndexError; nums[1:10] clamps to [2,3]
  • nums[1:10] raises IndexError

Answer: nums[10] raises IndexError; nums[1:10] clamps to [2,3]. A single out-of-range index raises IndexError, but an out-of-range slice clamps to what exists.

Continue this course

Frequently asked questions

Why is the stop index excluded in a slice?

Python slices are half-open: seq[start:stop] includes start but stops just before stop. This design has a neat payoff — the length of the slice is simply stop minus start, and seq[:i] plus seq[i:] always rebuilds the original with no overlap and no gap. Once you internalize 'stop is the first index NOT included', off-by-one mistakes mostly disappear.

What's the difference between a slice and an index?

An index like seq[2] returns a single element. A slice like seq[2:5] returns a new sub-sequence of the same type (a list from a list, a string from a string). Indexing out of range raises IndexError; slicing out of range never errors — it just clamps to what exists and may return an empty sequence.

Does slicing change the original list?

No — reading a slice always produces a brand-new object and leaves the original untouched. seq[:] is in fact the classic way to make a shallow copy of a list. The only way slicing mutates is when you assign TO a slice, like nums[1:3] = [9, 9], which replaces that section in place.

How does [::-1] reverse a sequence?

The third number in a slice is the step. A step of -1 walks the sequence backwards, and with both bounds omitted it spans the whole thing, so seq[::-1] returns a reversed copy. It works identically on strings and lists, which makes it the shortest idiom for 'give me this reversed'.