Product Dojo is a members' platform. Sign in to open the lessons, clear
stages, and earn the certificate for each one. Your rank and credentials
stay with your account across every device you use.
Free to join, through GitHub, Google or LinkedIn. No password to set.
Not unlocked yet
Stages open in order. Clear the one before this to come back here.
Names, emails, addresses, log lines, form fields, file contents — the
majority of what a program touches is text that arrived in the wrong shape.
This stage covers cutting text apart, cleaning it up, putting it back
together, and formatting a result someone can read.
Ready?
Step-by-step lessons
Cut, Clean, Rebuild, Present
Four short lessons: getting at pieces of a string, the methods that clean it up, breaking it into parts and gluing them back, and formatting the result with f-strings.
1
Every Character Has a Position
A string is a sequence of characters, each with a position counted from
zero. Not one. Zero.
word = "DOJO"
# 0123
word[0] # "D"
word[3] # "O"
word[-1] # "O" — negative counts back from the end
len(word) # 4
Because counting starts at zero, the last position is
len(word) - 1, never len(word). Reaching for the
latter gives you an IndexError, and that off-by-one is the
single most common mistake in this stage.
A slice takes a range: word[start:stop]
includes the start position and stops before the stop position.
[2:5]
A range
Positions 2, 3 and 4. Never 5.
[:3]
From the start
The first three characters.
[3:]
To the end
Everything from position 3 onwards.
[-4:]
The last four
Counting back from the end.
Why stop-before is the right rule
It looks arbitrary until you notice two things. text[:n]
and text[n:] always rejoin into exactly the original, with
nothing lost or duplicated. And the length of text[a:b] is
simply b - a. Both stop being true if the stop position is
included.
Quick check
Given code = "PY-2026", what does code[3:7] give you?
2
Methods: Functions Attached to a Value
A method is a function that belongs to a value. You call
it with a dot, and the value it is attached to is the thing it works on.
Every one of these hands back a new string. None of them
changes the original, because strings in Python are
immutable — they cannot be modified in place, ever.
Does nothing
name.upper()
On its own line. The new string is created and immediately thrown away.
Works
name = name.upper()
The result is assigned somewhere, so it survives.
Methods can be chained, because each one returns a string
that the next can be called on. Read them left to right, as a sequence of
steps: messy.strip().title() is "remove the outer spaces,
then fix the capitals".
Quick check
A program runs email.strip() on its own line, then compares email to a stored address and finds no match. Why?
3
Breaking Apart and Putting Back Together
split() cuts a string into a list of pieces.
join() glues a list of pieces back into one string. They are
exact opposites and they turn up together constantly.
split() with nothing in the brackets is a special case worth
knowing: it breaks on any run of whitespace and discards empty pieces, so
it copes with double spaces, tabs and line breaks without you thinking
about it.
join() reads backwards
Everyone gets this the wrong way round at first. The string you call
join() on is the glue, and the list of
pieces goes inside the brackets. So ", ".join(parts) means
"put a comma and a space between every piece". Not
parts.join(", ") — that is the other language you are
thinking of.
The reason it is worth the awkwardness: join() puts the
separator between pieces, never after the last one. Building the
same line by hand almost always leaves a trailing comma.
Quick check
You have parts = ["a", "b", "c"] and want "a-b-c". Which line does it?
4
f-strings: Writing the Sentence, Dropping in the Values
Put an f before the opening quote and anything inside
{braces} is replaced by its value:
The braces hold a whole expression, not just a name, so a small
calculation can live right where its result belongs.
After a colon comes a format spec, which controls how the
value is displayed. These three cover most of what you will need:
:.2f
Two decimal places
f"{37.5:.2f}" gives 37.50. This is how money prints.
:>6
Right-align in 6
Pads on the left, which is what lines numbers up in a column.
:,
Thousands separator
f"{1234567:,}" gives 1,234,567.
A debugging trick worth stealing
Put an = at the end of the expression inside the braces and
Python prints the expression as well as its value:
f"{total=}" produces total=37.5. When a program
is doing something you did not expect, scattering a few of those beats
guessing every time.
Once a sentence has more than one value in it, reach for an f-string.
Quick check
You need a total of 7.5 to print as 7.50. What goes in the braces?
Type it yourself
Code Lab
Four exercises on cutting, cleaning, joining and formatting text — the operations you will reach for more than any others.
0 of 4 cleared
Real Python runs right here in your browser — nothing to install, nothing
sent to a server. The interpreter downloads once the first time you press
Run, then stays cached.
01
Cut a piece out
To do
Every character in a string has a position, counted from
zero. word[0] is the first character.
word[2:5] is a slice: start at 2, stop
before 5. Leaving a side blank means "all the way" —
word[:3] is the first three characters and
word[3:] is everything from position 3 onwards. Negative
positions count back from the end, so word[-1] is the last
character.
Your task: from ticket, pull out
prefix (the first two characters), year (the
four digits) and name (everything after the last dash).
Print each on its own line.
your_code.py
PythonCtrl↵ to run
Hint
Count the positions: P is 0, Y is 1, the dash is 2, so the year starts at 3. ticket[3:7] gets the four digits, and ticket[-5:] grabs the last five characters.
Output
02
Clean up what people typed
To do
Text from humans arrives messy: stray spaces, inconsistent capitals, the
wrong separators. String methods fix that. A method is a
function attached to a value, called with a dot:
" Kenji ".strip() → "Kenji" (spaces off both ends) "KENJI".lower() → "kenji" "kenji".upper() → "KENJI" "kenji".title() → "Kenji" "a-b".replace("-", " ") → "a b"
Every one of these hands back a new string. The original
is untouched — strings in Python can never be modified in place, which is
why you always assign the result to something.
Your task: turn messy into
clean: no spaces at either end, and capitalised as a name.
The result must be exactly Kenji Tanaka.
your_code.py
PythonCtrl↵ to run
Hint
Two problems, two methods, and they chain: messy.strip() removes the outside spaces, and .title() fixes the capitals. Write it as one expression, left to right.
Output
03
Break apart and put back together
To do
split() cuts a string into a list of pieces, and
join() glues a list back into one string.
join() reads backwards the first few times: the string you
call it on is the glue, and the list goes inside the brackets.
", ".join(parts) means "put a comma and a space between every
piece".
Your task:raw holds three tags separated by
commas. Split them into tags, then build
headline by joining them with " · " (a
space, a middle dot, a space). Print the number of tags, then the
headline.
your_code.py
PythonCtrl↵ to run
Hint
tags = raw.split(",") gives you the list. Then " · ".join(tags) glues them back with the separator you want. len(tags) counts them.
Output
04
Drop values straight into text
To do
Building a sentence out of values with commas and plus signs gets ugly
fast. An f-string lets you write the sentence and drop
the values in where they belong. Put f before the opening
quote and any {expression} inside gets replaced by its value.
name = "Kenji" xp = 600 f"{name} has {xp} XP" → "Kenji has 600 XP"
Anything can go in the braces, including a calculation:
f"{xp * 2} XP". A format spec after a colon controls how a
number is displayed — f"{value:.2f}" shows exactly two
decimal places, which is how you print money without a mess of digits.
Your task: build summary as an f-string
reading exactly Kenji cleared 3 stages for 600 XP, where the
600 is calculated rather than typed. Then build price_line
reading exactly Total: 37.50, with two decimal places. Print
both.
your_code.py
PythonCtrl↵ to run
Hint
f"{name} cleared {stages} stages for {stages * xp_each} XP" — the maths can live inside the braces. For the money, f"Total: {total:.2f}".
Output
Boss round
Assignment: Tidy the Sign-Up
One graded task. Pass every check and the stage is cleared and the title is yours — one more stage towards the Python Practitioner certificate.
This one is graded. You can see 2
of the checks up front, and 4
stay hidden until you run. The hidden ones test the same job from angles
you have not been shown, so code that genuinely solves the problem clears
this and code shaped around the visible examples does not.
Pass them all and the stage is yours.
Tidy up a sign-up
To do
A sign-up form has handed you one messy line. Pull it apart, clean each
piece, and print a tidy record.
The raw value is " kenji TANAKA | KENJI@Example.COM | Osaka "
— three fields separated by a vertical bar, with random spacing and
capitals.
Create these four variables:
full_name — Kenji Tanaka, capitalised as a name
email — kenji@example.com, all lowercase
city — Osaka
initials — KT, built from the first letter of each part of the name
Then print exactly three lines, using f-strings:
Kenji Tanaka (KT) kenji@example.com City: Osaka
Every field must be derived from raw. The hidden checks
confirm the pieces were actually extracted rather than retyped.
your_code.py
PythonCtrl↵ to run
Hint
Each piece from split still has spaces around it, so .strip() every one. Then .title() the name and .lower() the email. For the initials, split the cleaned name and take character [0] of each part.
Output
Lock it in
Guess the Term
Read the clues and name the concept. The fewer clues you need, the more points you score.
Round 1Score 0
Keep it handy
Strings Quick Reference
The whole stage on one screen.
Positions
[0]
First character
Counting starts at zero.
[-1]
Last character
Negatives count back from the end.
[a:b]
Slice
From a, stopping before b. Length is b - a.
Cleaning
.strip()
Whitespace off both ends.
aA
.lower().upper().title()
Force the capitalisation.
.replace(a, b)
Every occurrence, not just the first.
Structure
.split(sep)
String to list. Empty brackets splits on any whitespace.