Python Quest

Track 7 · Lesson 1 of 11

Strings in depth

How Python strings really behave: immutability, indexing and slicing, looping over characters, escape sequences and raw strings.

About 35 minBeginner4 exercisesFree

You will be able to

  • Explain why strings are immutable and build a changed copy instead of editing one in place
  • Index and slice strings with positive and negative positions and a step
  • Loop over the characters of a string and test membership with `in`
  • Write special characters with escape sequences and use raw strings for backslash-heavy text

Most data that reaches a program arrives as text: a customer typing their name into a form, an order reference printed on an invoice, a line in a server log, a message from a support chat. Before you can clean, check or analyse any of it, you need a precise picture of what a string is and what you can and cannot do with one.

You have already met strings, len() and a few string methods. This lesson goes one level deeper: how Python stores a string as a sequence of characters, why you can never change a string in place, how to walk through it character by character, and how to write characters that are hard to type, such as a tab, a newline or a backslash.

A string is a sequence of characters

Every character in a string has a position called an index. The first character is at index 0. Negative indexes count from the end: -1 is the last character, -2 the one before it.

A slice text[start:stop] gives you a new string from start up to, but not including, stop. Leave out start to begin at the start; leave out stop to run to the end. A third number, the step, takes every n-th character, and a step of -1 walks backwards.

Indexing and slicing an invoice reference
ref = "INV-2025-00417"

print(ref[0])        # first character
print(ref[-1])       # last character
print(ref[:3])       # the prefix
print(ref[4:8])      # the year
print(ref[-5:])      # the last five characters
print(ref[::2])      # every second character
print(ref[::-1])     # reversed
print(len(ref))

Try it: Change the slice for the year to ref[4:9] and notice that it now includes the second dash. Then try ref[50] and ref[50:] to compare an index and a slice that go past the end.

Two details are worth remembering:

  • An index that is out of range raises IndexError, but a slice that is out of range never does. ref[50:] is simply "", the empty string.
  • ref[-5:] reads as "the last five characters". It is the cleanest way to take a suffix of any length.

Strings are immutable

Immutable means "cannot be changed after it is created". Every string method and every slice gives you a new string; the original stays exactly as it was. If you try to assign to a position, Python refuses:

You cannot replace a character in place
code = "sku-1042"
code[0] = "S"

Try it: Read the error message: 'str' object does not support item assignment. Now replace the second line with code = "S" + code[1:] and print(code).

To "change" a string you build a new one and, if you want, store it back under the same name. The old string is discarded.

A method such as upper() follows the same rule. Calling name.upper() on its own line does nothing useful: it creates an upper-case copy and throws it away. You must keep the result.

Building a new string and keeping the result
code = "sku-1042"
fixed = code[:3].upper() + code[3:]
print(code)    # unchanged
print(fixed)

name = "noor"
name.upper()           # result discarded
print(name)
name = name.upper()    # result kept
print(name)

Try it: Add a line that builds the string "SKU_1042" from fixed by joining two slices around an underscore.

Looping over characters and testing with in

A for loop over a string gives you one character at a time, from left to right. Combined with the in operator you can classify each one. in works two ways on strings:

  • ch in "aeiou" checks whether one character is one of the listed characters.
  • "refund" in message checks whether a whole substring appears anywhere inside a longer string.
Counting digits and checking for a word
message = "Order 5521 arrived damaged, please send a refund for 2 items"

digits = 0
for ch in message:
    if ch in "0123456789":
        digits += 1
print("Digits:", digits)

print("refund" in message)
print("Refund" in message)   # in is case-sensitive
print("" in message)          # the empty string is in every string

Try it: Count the spaces in the message as well, using a second counter inside the same loop.

Predict the output

code = "SKU-48213-BLK"
print(code[4:9])
print(code[-3:])
print(code[::-1][:3])
print("482" in code, "blk" in code)

Escape sequences

Some characters cannot be typed directly inside quotes. An escape sequence is a backslash followed by a code that stands for one character:

You writeYou get
\na newline (line break)
\ta tab
\\one backslash
\" and \'a quote that does not end the string
\u00e9the character with that Unicode number (here é)

Each escape counts as one character: len("a\tb") is 3.

Escapes in a short report
report = "Item\tQty\nPens\t12\nPaper\t3"
print(report)
print(len("a\tb"))
print("She said \"thanks\" and left")
print('It\'s ready')
print("Caf\u00e9 order")

Try it: Add a third row for Staplers with a quantity of 7 to the report string, using \t and \n.

Raw strings

Backslashes become a problem when the text itself is full of them, such as Windows folder paths or (later in this track) regular expressions. In "C:\new\tables" Python sees \n and \t and quietly turns them into a newline and a tab.

A raw string has an r before the opening quote: r"C:\new\tables". Inside it, a backslash is an ordinary character and nothing is escaped. One restriction: a raw string cannot end with a single backslash, because \" still stops the quote from closing the string.

A path written two ways
normal = "C:\new\tables"
raw = r"C:\new\tables"
print(normal)
print(raw)
print(len(normal), len(raw))
print(raw == "C:\\new\\tables")

Try it: Print repr(normal) and repr(raw). repr() shows the escapes Python is holding, which is a quick way to spot an accidental newline.

Guided practice · ●difficulty 1 of 5 · about 4 min

Count the vowels

A naming tool for a toy shop scores product names by how many vowels they contain. Write a function count_vowels(text) that returns how many characters in text are vowels: a, e, i, o or u, in either upper or lower case.

count_vowels("Invoice approved")   # 7
count_vowels("RHYTHM")             # 0
count_vowels("")                   # 0

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Challenge · ●●difficulty 2 of 5 · about 6 min

Mask a reference number

When a support agent reads out a customer's booking reference on a recorded call, only the last four characters may be shown. Write mask_reference(ref) that returns a new string where every character except the last four is replaced by *. References of four characters or fewer are returned unchanged.

mask_reference("INV-2024-0042")   # "*********0042"
mask_reference("12345")           # "*2345"
mask_reference("AB12")            # "AB12"

Spaces and dashes in the hidden part are masked too.

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Debugging · ●●difficulty 2 of 5 · about 5 min

The broken report path

An export script builds the full Windows path of a report file. The finance team says the files never appear in C:\shared\new_orders\totals, and when the path is printed it is split across two lines with a strange gap in the middle.

Fix report_path(filename) so that it returns the folder path, a single backslash and then the file name:

report_path("march.csv")   # C:\shared\new_orders\totals\march.csv  (as printed)

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Debugging · ●●difficulty 2 of 5 · about 5 min

Capitalise without crashing

A customer-records tool should make the first letter of a company name upper case and leave every other character exactly as typed. Right now it crashes with TypeError: 'str' object does not support item assignment.

Fix capitalise_first(name) so that:

capitalise_first("riyadh branch")       # "Riyadh branch"
capitalise_first("al-Rashid Trading")   # "Al-Rashid Trading"  (the R stays upper case)
capitalise_first("")                    # ""

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Summary

  • A string is an immutable sequence of characters: methods and slices return new strings, and you keep a result by assigning it.
  • Indexes start at 0 and negative indexes count from the end; slices exclude the stop position and never raise IndexError.
  • A for loop visits each character; in tests a single character or a whole substring, case-sensitively.
  • Escape sequences such as \n, \t and \\ each stand for one character; raw strings (r"...") keep backslashes literally.