Python String Indexing
Introduction: Strings as Ordered Sequences
Welcome to Manipulating Strings in Python! Text shows up everywhere in real programs: usernames on a login screen, filenames in a folder, product codes on an invoice. Before we can clean, split, or reformat any of that text, we need one basic skill: reaching a single character inside a string.
A Python string is an ordered sequence of characters. "Ordered" means the characters have fixed positions that never shuffle on their own, and each position carries a number we can use to pull that character out. Our running example is a simple username:
Positive Indices Start at Zero
To reach one character, we write the variable name followed by square brackets containing a position number: string[index]. Python counts positions starting from 0, not from 1 — the single most common source of confusion for beginners. Each character has an index, a number representing its position that we can use to access that character.
| Character | c | o | d | e | s | i | g | n | a | l |
|---|---|---|---|---|---|---|---|---|---|---|
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
A ten-character string has indices 0 through 9, never 10. The mental shift to make is that the third character sits at index 2, because counting begins one step earlier than we say it out loud.
Accessing Characters by Position
Let's put the bracket syntax to work:
Reading the table above, username[0] lands on "c" and username[2] lands on "d". One detail worth locking in: indexing gives us back a string of length one, not some special "character" type. The result behaves like any other string and can be printed, compared, or joined with other text.
Negative Indices Count from the End
Often we care about the end of a string without knowing how long it is: the last letter of a name, the final digit of an order code. Counting forward would force us to measure first. Python solves this with negative indices, which count backward from the right: -1 is the last character, -2 the second-to-last, and so on.
| Character | c | o | d | e | s | i | g | n | a | l |
|---|---|---|---|---|---|---|---|---|---|---|
| Positive index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| Negative index | -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 |
The asymmetry is worth memorizing: positive indices start at 0, negative indices start at -1. There is no -0, since 0 already means the first character.
Grabbing the Last Character
With negative indexing available, the final character takes one short expression:
Here username[-1] reaches the rightmost character of "codesignal", which is "l". The nice part is that this expression works unchanged for any non-empty string, whether it holds three characters or three hundred — we never have to measure anything first.
Measuring Length with len()
Sometimes we genuinely need to know how long a string is: to check whether a password is long enough, or to find a midpoint. Python's built-in len() function returns the number of characters:
We pass the string into len() and store the answer in a variable so we can reuse it. Two clarifications:
len()counts every character, including spaces, digits, and punctuation, so"hi there!"has length9.len()returns a count, not an index; the count for"codesignal"is10, even though the highest valid index is9.
Connecting len() and Indexing
That last point gives us the rule that ties both ideas together: for any string, the valid positive indices run from 0 up to len(string) - 1. So we can reach the final character with arithmetic instead of a negative index:
Since length holds 10, this evaluates to username[9], which is "l" — exactly what username[-1] gave us. The - 1 is not optional: username[10] would point one step past the end and raise an IndexError.
Every index expression requires the character it asks for to actually exist. That is why an empty string "" has no valid indices at all — not even 0 or -1 — and every one of these lookups would raise IndexError on it. Throughout this unit we work with non-empty strings, so we are safe; later, when values come from real user input, checking that a string has content first becomes part of the job.
Reaching the Middle with Floor Division
Now for a position that truly needs the length: the middle. Dividing with / produces a decimal such as 5.0, and indices must be whole numbers. So we use //, the floor division operator, which divides and discards any fractional part:
With length equal to 10, the expression 10 // 2 gives 5, so we ask for username[5], which is "i".
Be precise about what "middle" means here, because it depends on the length:
- Odd length, such as
"cat": there is one true centre, and3 // 2gives1, hitting"a"exactly. - Even length, such as our ten-character username: there are two central characters,
sat index4andiat index5. Thelen(s) // 2pattern always picks the right-hand one of the pair.
That right-hand choice is the convention we will use for the rest of the course, so "codesignal" gives "i" rather than "s".
The Complete Program
Every piece we built, gathered into one file — this is the program the practices will walk you through building yourself:
Conclusion and Next Steps
Four tools, one habit: pick a position, put it in brackets. Positive indices count from 0, negative indices count from -1, len() measures, and len(s) - 1 and len(s) // 2 land on the last and middle positions of a non-empty string.
In Unit 2 we scale up from single characters to whole chunks of text with slicing. First, let's make these positions automatic — the practices are waiting.
