First exams 2027 · SL & HL

B2.1.2 · Programming fundamentals

Extracting and manipulating substrings

A string is built out of smaller pieces. This statement is about pulling out exactly the piece you need, then using it to build, correct, or replace what's already there.

Core for SL & HL Typically 3–5 marks · AO2/AO3 Command term: construct

Explainer

GreenLine Bus Co. issues every booking as one string of text, printed straight onto the ticket: Isla Mitchell|24B|0930|RTN. A human reads that at a glance, name, route, time, return trip. A program cannot. It has to pull the route number out on its own to show it on the departure board, pull the time out to sort tickets by departure, and sometimes go back in and fix one piece, like correcting a route after a platform change, without disturbing the rest of the string. All of that starts with the same skill: taking a smaller piece out of a bigger string.

Df

A substring is any sequence of consecutive characters taken from within a larger string. "24B" is a substring of the booking string above; so is "Isla Mitchell", and so is the single character "|". The original string is never changed by taking one, a substring is always a fresh, separate piece of text.

Df

The statement asks for two different skills, in this order. Extracting means identifying where a substring starts and ends, then reading it out, a purely read-only operation. Manipulating means doing something with what you extracted: altering it (changing its case or content), concatenating it onto other text to build something new, or replacing it back into the original string.

Strings never change in place

Every operation on this page, slicing, find(), replace(), upper(), produces a brand new string. None of them edit the original characters directly, in either Python or Java, both languages treat strings as immutable. That is why every example on this page reassigns the result to a variable: ticket = ticket.replace(old, new) keeps the new version only because it was written back into ticket, the call on its own changes nothing.

Explainer

Five operations, in Python and Java

The syllabus names extracting plus three kinds of manipulating, altering, concatenating, replacing, and every one of them shows up in the worked example below. Both languages are examinable, so read each row across.

OperationWhat it doesPythonJava
Extract by positionTake characters from a start index up to, but not including, an end indexticket[4:7]ticket.substring(4, 7)
Extract to the endTake every character from a start index onwardticket[4:]ticket.substring(4)
Search for a substringReturn the index of the first match, or a not-found signalticket.find("|") → index or -1ticket.indexOf("|") → index or -1
Replace ONE characterProduce a new string with every occurrence of a single character swappedticket.replace(old, new)ticket.replace('e', '@')
Replace MULTIPLE charactersProduce a new string with every match of a whole piece of text swappedticket.replace(old, new)ticket.replaceAll("is", "will be")
ConcatenateJoin smaller pieces of text into one longer stringa + ba + b
Alter (change case)Produce a new string with different capitalisationticket.lower()ticket.toLowerCase()
Two boundaries worth memorising

A slice's end index is exclusive: ticket[4:7] takes the characters at positions 4, 5 and 6, never position 7. Java's substring(begin, end) follows the identical rule, so the two columns above always take the same three characters for the same numbers. And find()/indexOf() return -1, not an error, when the substring genuinely isn't there, code that assumes it was always found will silently use position -1 and slice the wrong thing.

Java splits replace into two functions, Python doesn't

Python's replace() swaps every occurrence of a piece of text, whether it's one character or many, in a single function. Java splits this into two: replace() for a single character, and replaceAll() when the thing being swapped is more than one character. Reaching for plain replace() in Java when the target is a whole word is a real, easy mistake to make.

Concatenation has more than one form worth recognising too. + is the most direct, but Python also offers f-strings for building a message out of several pieces at once: f"{name} - Route {route} at {time}" produces exactly the same result as chaining + between each piece, just without the repeated operators.

Worked example

Parsing a booking code, one field at a time

The booking string has four fields separated by three | characters, and the fields are different lengths each time (a passenger's name is never a fixed number of characters), so fixed positions won't work. The program has to search for each delimiter before it can slice.

Why one find() is not enough

Calling ticket.find("|") three times in a row with no change would return the exact same position three times, the first one, every time. Each later call has to start searching just past the previous match, which is what the second argument to find(sub, start) is for.

Extracting and using the four fields Step 1 of 6

Step 1. The raw booking string arrives. Nothing has been extracted yet, it is one piece of text with no structure the program can see.

ticket = "Isla Mitchell|24B|0930|RTN"
Extracted so far
ticket = "Isla Mitchell|24B|0930|RTN"

Step 2. Find the three delimiter positions. Each call searches from just after the last match, so the same character further along the string is never re-found by mistake.

p1 = ticket.find("|") # 13 p2 = ticket.find("|", p1 + 1) # 17 p3 = ticket.find("|", p2 + 1) # 22
Extracted so far
ticket = "Isla Mitchell|24B|0930|RTN"
p1 = 13
p2 = 17
p3 = 22

Step 3. Slice out the four fields. Each slice's end is exclusive, so ticket[:p1] stops right before the first |, and never includes it.

name = ticket[:p1] # "Isla Mitchell" route = ticket[p1+1:p2] # "24B" time = ticket[p2+1:p3] # "0930" trip_type = ticket[p3+1:] # "RTN"
Extracted so far
ticket = "Isla Mitchell|24B|0930|RTN"
p1 = 13, p2 = 17, p3 = 22
name = "Isla Mitchell"
route = "24B"
time = "0930"
trip_type = "RTN"

Step 4. Concatenate a friendly summary line. The four extracted pieces, plus some fixed text, are joined into one new string with +. None of the original variables change.

summary = name + " - Route " + route + " at " + time
Extracted so far
name = "Isla Mitchell", route = "24B", time = "0930"
summary = "Isla Mitchell - Route 24B at 0930"

Step 5. Replace the route after a platform change. replace(old, new) swaps every occurrence of the old substring, then the result is written back into ticket, since replace() on its own would just be thrown away.

ticket = ticket.replace(route, "31A")
Extracted so far
ticket = "Isla Mitchell|31A|0930|RTN"
route still holds the OLD value, "24B", replace() never updates the variables used to build its arguments

Step 6. Alter the trip type for a printed label. lower() returns a new string in lower case; the original trip_type is untouched unless the result is reassigned to it.

label = trip_type.lower() # "rtn"
Extracted so far
trip_type = "RTN"
label = "rtn"

Try it yourself

Run the booking-code parser

The same program as the steps above, live and editable. Two experiments are waiting at the bottom: change the passenger's name to something longer and confirm the rest still parses correctly, then try searching for a delimiter that isn't in the string and see what find() actually returns.

main.py
Loading Python engine…
Output
Press Run to see the output here.

Running in offline fallback mode. The full Python engine couldn't load (blocked school network, or no internet), so this console switched automatically to a small hand-written engine. It supports slicing, find(), replace(), upper() and lower() on strings, exactly what this page needs, but it isn't full Python.

Check your understanding

A string code = "R12-450". What does code.find("-") return, and what does code[:code.find("-")] then produce?

Common mistake

Markers reported this pattern for B2.1.2-style substring questions

RecurringMarkers report two things going wrong most often on questions that chain several string operations together: substring extraction itself applied with the wrong indices or boundaries, and the extracted pieces then joined back together in the wrong order, or with a piece dropped entirely, when building the final result.

A specific version of the joining mistake is reusing find() with no start position once a delimiter appears more than once in the string. Every call after the first returns the SAME first match again, so every field after the first gets built from the wrong positions, and the pieces joined at the end come out in the wrong order or missing a piece.

The boundary mistake is usually the exclusive-end rule: treating a slice's end index as inclusive. ticket[4:7] is three characters, at positions 4, 5 and 6, not four, and a solution that is off by one in the end index silently drops or duplicates the character right at the boundary.

Answer frame that scores: work out every delimiter position first, always passing the previous position onward as the next search's start → slice with the exclusive-end rule firmly in mind → reassign the result of replace()/upper()/lower() back to a variable, since none of them change the original string on their own.

✕

What the bug looks like: p2 = ticket.find("|") written the exact same way as p1, with no second argument. p2 ends up equal to p1, so ticket[p1+1:p2] slices a string with its start AFTER its end, and produces an empty string instead of the route code.

✓

What it should look like: every find() call after the first passes previous_position + 1 as its own start argument, so the search genuinely moves forward through the string instead of finding the same character again.

Revision questions

Past-paper style. Write an answer, then reveal the marking points and mark yourself.

Question 1 · 2 marks

[2]Distinguish between extracting a substring and replacing a substring.

Question 2 · 3 marks

[3]A string code = "SCR14-A7-2130" holds a screen number, seat and showtime separated by dashes. Construct code to extract all three fields.

Question 3 · 4 marks

[4]Explain why ticket.replace("24B", "31A") on its own does not change the value stored in ticket, and state what must be done instead.

Flashcards

Active recall for this statement’s key terms.