SECTION 1
Historical Context & Motivation
Working with text is one of the most common tasks in programming. Whether you are building a search engine, filtering usernames, or parsing data from a file, you constantly need to pull out specific characters or sections of a string. Early programming languages made this surprisingly difficult, often requiring complex pointer arithmetic just to grab a single letter. Python changed this by introducing an elegant, readable syntax for indexing and slicing that has since influenced many other languages.
1960s • Early String Handling
1972 • C and Pointer Arithmetic
1987 • ABC Language's Clean Syntax
1991 • Python 0.9 Released
2008–Today • Python 3 and Unicode
The core question this lesson answers is straightforward: How do you efficiently access individual characters and substrings within a Python string? Understanding indexing and slicing gives you the tools to manipulate text data with precision and confidence.
SECTION 2
Core Principles & Definitions
Before you start indexing and slicing, you need to understand a few foundational ideas about how Python organizes the characters inside a string. A string in Python is a sequence — an ordered collection of characters where each character occupies a numbered position. These positions are called indices (singular: index). Python's indexing system has a few key traits that set it apart.
Zero-Based Indexing
0, not 1. This convention comes from how computers measure offsets in memory. The second character is at index 1, and so on.Negative Indexing
-1 is the last character, -2 is the second-to-last, and so on. This eliminates the need to calculate the length of the string first.Immutability
Slicing with [start:stop:step]
start index (inclusive), a stop index (exclusive), and an optional step (how many positions to skip). This three-part notation is incredibly powerful.Graceful Bounds Handling
IndexError, but slicing never does — it simply returns what is available. This makes slicing forgiving and safe for variable-length data.KEY TAKEAWAY
SECTION 3
Visual Explanation — Index Map
The diagram below shows the string "PYTHON" laid out character by character. Above each character you see the positive index, and below it you see the corresponding negative index. Notice how the two numbering systems always point to the same character.
"PYTHON". The numbers above the boxes are the positive indices (starting from 0 on the left), and the pink numbers below are the negative indices (starting from −1 on the right). For example, "PYTHON"[0] and "PYTHON"[-6] both give you 'P'.This dual numbering system is one of Python's most convenient features. You can always reach the last character with s[-1] no matter how long the string is. There is no need to first call len(s) and then subtract 1 — Python handles that for you behind the scenes.
SECTION 4
How Indexing & Slicing Work
Single-Character Indexing
To retrieve one character, place a single integer inside square brackets after the string variable or literal. The general form is:
INDEXING SYNTAX
If index ≥ 0, it counts from the left (starting at 0). If index < 0, Python internally converts it to len(string) + index so that −1 always refers to the last character.
Slice Notation
Slicing uses up to three parameters separated by colons. Each parameter is optional and has a sensible default.
SLICING SYNTAX
start — the index where the slice begins (inclusive, default 0). stop — the index where the slice ends (exclusive, default len(string)). step — how many positions to advance each time (default 1). A step of −1 reverses the string.
Negative Index Conversion
NEGATIVE INDEX FORMULA
For example, if string = "PYTHON" (length 6) and negative_index = -2, then effective_index = 6 + (−2) = 4, which corresponds to 'O'.
Watch Out for Off-by-One Errors
stop value is exclusive. Writing s[1:3] gives you indices 1 and 2, but not 3. Think of stop as a fence — the slice goes up to it but never includes it.SECTION 5
Common Slice Patterns & Detailed Breakdown
Once you understand the basic [start:stop:step] notation, you can combine defaults and negative values to produce a variety of useful patterns. The diagram below illustrates several common slices applied to the string "ABCDEFGH".
s = "ABCDEFGH". Note how omitting start defaults to 0, omitting stop defaults to the end, and a step of -1 reverses the string entirely.| Slice Expression | start | stop | step | Result |
|---|---|---|---|---|
s[2:5] | 2 | 5 | 1 (default) | "CDE" |
s[:4] | 0 (default) | 4 | 1 (default) | "ABCD" |
s[5:] | 5 | 8 (default) | 1 (default) | "FGH" |
s[::2] | 0 (default) | 8 (default) | 2 | "ACEG" |
s[::-1] | end | beginning | −1 | "HGFEDCBA" |
s[-3:] | −3 → 5 | 8 (default) | 1 (default) | "FGH" |
s[1:-1] | 1 | −1 → 7 | 1 (default) | "BCDEFG" |
Summary of slice parameters and results for s = "ABCDEFGH"
SECTION 6
Worked Example — Extracting a Domain from an Email
Let's solve a realistic problem: given the email address email = "student@school.edu", extract just the domain name "school.edu" using indexing and slicing.
Extracting a Domain from an Email Address
- 1
Step 1 — Identify the string and goal
We haveemail = "student@school.edu". The domain starts right after the@symbol and runs to the end of the string. Our task is to find the position of@and slice everything that comes after it. - 2
Step 2 — Find the index of '@'
We useemail.find("@")which returns the index of the first occurrence of"@". Let's count: s, t, u, d, e, n, t — that is indices 0 through 6. The@is at index 7.at_pos = 7 - 3
Step 3 — Slice from one past '@' to the end
The domain starts at indexat_pos + 1which is 8. We slice to the end by omitting the stop value:domain = email[8:] - 4
Step 4 — Verify the result
Runningprint(domain)outputs the substring starting at index 8 through the end of the string.Output:school.edu - 5
Step 5 — Combine into one clean expression
In practice, you can combine these into a single line. The expressionemail[email.find('@') + 1:]finds the@and slices everything after it in one step. This is a pattern you will see frequently in real-world code."school.edu"
SECTION 7
Strengths, Limitations & Common Pitfalls
| Feature | Strength | Pitfall / Limitation |
|---|---|---|
| Zero-based indexing | Consistent with lists, tuples, and most programming languages | Off-by-one errors are common for beginners who expect counting from 1 |
| Negative indexing | Quick access to end of string without knowing its length | Can be confusing when mixed with positive indices in slices |
| Exclusive stop | Makes calculating slice length easy: stop − start = length | Beginners often include one too few characters |
| Step parameter | Powerful for reversing, skipping, or sampling characters | Negative step with start/stop requires careful coordinate thinking |
| Immutability | Strings are safe to pass around — no accidental changes | Cannot assign to an index; must build a new string instead |
Strengths and pitfalls of Python string indexing and slicing
KEY TAKEAWAY
stop value might seem annoying at first, but it has a beautiful property: the length of any slice s[a:b] is exactly b − a characters, and adjacent slices s[:k] + s[k:] always reconstruct the original string. This is like cutting a ribbon at position k — the two pieces always rejoin perfectly with no overlap and no gap.SECTION 8
Connection to Lists, Tuples & Advanced Topics
One of the most powerful aspects of Python's indexing and slicing syntax is that it works identically across all sequence types. Once you master slicing on strings, you already know how to slice lists, tuples, and even ranges. The table below compares how the same syntax applies to strings versus lists.
| Feature | Strings | Lists |
|---|---|---|
| Element type | Single characters (strings of length 1) | Any Python object |
| Mutable? | No — cannot assign to an index | Yes — can assign and also do slice assignment |
x[2:5] | Returns a new string | Returns a new list (shallow copy) |
x[::-1] | Reversed string | Reversed list |
| Negative indexing | Fully supported | Fully supported |
Comparing string slicing and list slicing
As you progress, you will encounter slice objects created with slice(start, stop, step) which let you store a slicing pattern in a variable and reuse it. Libraries like NumPy extend slicing to multiple dimensions for working with matrices and arrays. The foundation you build today transfers directly to those advanced tools.
SECTION 9
Practice Problems
Try these five problems to solidify your understanding. They progress from simple recall to creative problem solving. For each, assume s = "COMPUTATION" unless stated otherwise.
Question 1: CONCEPTUAL
s[0] gives the first character of a string, while s[-1] gives the last. Why did Python's designers choose to start indexing at 0 instead of 1?Show answer
s[-1] becomes s[len(s) + (-1)] which equals s[len(s) - 1], the last valid index. Zero-based indexing also simplifies slice arithmetic: the length of s[a:b] is always b - a.Question 2: BASIC CALCULATION
s = "COMPUTATION", what does each of the following evaluate to? (a) s[3] (b) s[-4] (c) s[4:8]Show answer
s[3] → 'P'. (b) s[-4] converts to s[11 + (-4)] = s[7] → 'T'. (c) s[4:8] includes indices 4, 5, 6, and 7 → "UTAT".Question 3: INTERMEDIATE
s = "COMPUTATION" and produces the string "CUP". Hint: think about step values and negative indexing.Show answer
s[0] + s[4] + s[3] which concatenates 'C', 'U', and 'P'. A pure-slicing approach is trickier because the letters are not evenly spaced. However, you could also use s[::4][:2] + s[3]: s[::4] gives 'CAIN', and [:2] trims it to 'CA'... so the simple indexing + concatenation approach is best. This illustrates that slicing is great for contiguous or evenly-spaced substrings, but individual indexing is better for arbitrary characters.Question 4: APPLIED
phone = "(555) 867-5309". Using only indexing and slicing, extract the area code "555" and the last four digits "5309". Show your expressions.Show answer
phone[1:4] extracts indices 1, 2, and 3 → "555". For the last four digits: phone[-4:] extracts the last four characters → "5309". Using negative indexing for the last four digits is more robust because it works regardless of the phone number format's total length.Question 5: CRITICAL THINKING
def is_palindrome(word): return word == word[::-1]Show answer
word[::-1] creates a reversed copy of the string by stepping backward through every character. The function then compares the original string to this reversed version; if they match, the word is a palindrome. This is elegant and works perfectly for simple cases like "racecar". However, it is case-sensitive, so "Racecar" would return False because 'R' ≠ 'r'. A more robust version would normalize the string first with word.lower() and possibly strip non-alphabetic characters for phrases like "A man, a plan, a canal: Panama".SUMMARY
Lesson Summary
Summary
Python strings are ordered sequences of characters that support zero-based indexing — the first character is at position 0. You can access individual characters with bracket notation like s[i], and Python's negative indexing lets you count from the end, where s[-1] is always the last character. Slicing uses the syntax s[start:stop:step] where start is inclusive, stop is exclusive, and step controls direction and spacing.
Key patterns to remember: s[:k] gets the first k characters, s[-k:] gets the last k, and s[::-1] produces a reversed copy. Because strings are immutable, every slice creates a new string without altering the original. This same indexing and slicing syntax applies to lists and tuples, making it one of the most transferable skills in Python.