Text Is Everywhere
Think about everything you do on a computer or phone: you type usernames, send messages, create passwords, enter search queries, fill in forms, and read web pages. Every single one of those activities involves text — and in Python, text is stored as strings.
Strings are sequences of characters — letters, digits, spaces, punctuation, even emojis. Python gives you a powerful set of tools to work with them: you can pull out individual characters, extract sections, search for words, change capitalisation, replace parts of a sentence, and much more.
In the real world, string manipulation is everywhere:
- Spell checkers compare what you typed against a dictionary and suggest corrections
- Search engines break your query into words, strip out punctuation, and match against millions of pages
- Password validators check that your password is long enough and contains the right mix of characters
- Social media platforms detect hashtags, mentions, and links inside your posts
By the end of this chapter, you will be able to:
- Use indexing and slicing to extract characters and sections from a string
- Use built-in string methods to transform and inspect text
- Search within strings to find characters, words, and patterns
- Split strings into lists and join lists back into strings
- Traverse strings character by character using loops
- Understand ASCII and Unicode character encoding
- Format and combine text for clear, readable output
- Build practical programs like email validators and ciphers
See It in Action
Example 1: String Indexing and Slicing
Every character in a string has a position number called an index. Python starts counting from 0, not 1. You can use these positions to pull out individual characters or “slices” of text. Click Run Code to see how it works:
message = "Hello World"
# Indexing - get a single character
print(message[0]) # First character
print(message[6]) # Seventh character (index 6)
# Slicing - get a section of the string
print(message[0:5]) # Characters from index 0 up to (not including) 5
print(message[-5:]) # Last 5 characters
# The full string, backwards
print(message[::-1])
Notice that message[0:5] gives us Hello — it starts at index 0 and stops before index 5. The slice message[-5:] uses a negative index to count from the end, giving us World. And [::-1] is a neat trick that reverses the entire string!
message[6:11] or use negative indices like message[-3:] to grab the last three characters. What happens if you try message[0:5:2]? (That third number is the step.)
Example 2: String Methods
Python strings come with dozens of built-in methods — special functions that you call using a dot after the string. These let you transform text in all sorts of useful ways:
text = " Hello, Python Learners! "
# Changing case
print(text.upper()) # ALL CAPS
print(text.lower()) # all lowercase
print(text.title()) # Title Case
# Removing whitespace
print(text.strip()) # Remove spaces from both ends
# Replacing text
print(text.replace("Python", "Amazing"))
# Counting and finding
message = "abracadabra"
print(message.count("a")) # How many times does 'a' appear?
print(message.find("cad")) # At which index does 'cad' start?
Each of these methods returns a new string — the original is never changed. The .strip() method is especially useful when processing user input, since people often accidentally add extra spaces. The .find() method returns -1 if the text is not found.
text.strip().upper(). What does "hello world".replace("o", "0").replace("l", "1") do? Can you use .count() to count how many times the letter “e” appears in a sentence of your choice?
Example 3: String Checking
Sometimes you don’t want to change a string — you just want to check something about it. Python has methods that return True or False, and you can also use the in keyword to search for text within a string:
# Checking what a string starts or ends with
filename = "homework.pdf"
print(filename.startswith("home")) # True
print(filename.endswith(".pdf")) # True
print(filename.endswith(".docx")) # False
# Checking character types
pin = "1234"
word = "Hello"
mixed = "abc123"
print(pin.isdigit()) # True - all characters are digits
print(word.isalpha()) # True - all characters are letters
print(mixed.isalpha()) # False - contains digits too
# Using 'in' to search within a string
sentence = "The quick brown fox jumps over the lazy dog"
print("fox" in sentence) # True
print("cat" in sentence) # False
print("fox" not in sentence) # False
The in keyword is incredibly useful — it checks whether one string appears anywhere inside another. Combined with if statements, this lets you build things like search features and input validation. The .startswith() and .endswith() methods are perfect for checking file extensions or URL prefixes.
in. What does "12.5".isdigit() return, and why? Test "HELLO".isupper() and "hello".islower(). Can you think of a real situation where .endswith() would be useful?
Example 4: The split() and join() Methods
Two of the most powerful string methods are .split() and .join(). The .split() method breaks a string into a list of smaller strings at a specified separator (called a delimiter). The .join() method does the opposite — it takes a list of strings and glues them back together. These are essential for working with structured data like CSV files:
# Splitting a sentence into words (splits on spaces by default)
sentence = "Python is a powerful language"
words = sentence.split()
print(words)
print("There are", len(words), "words")
# Splitting CSV-like data on commas
csv_line = "Alice,17,Computer Science,A*"
fields = csv_line.split(",")
print(fields)
print("Name:", fields[0])
print("Age:", fields[1])
print("Subject:", fields[2])
print("Grade:", fields[3])
# Joining a list back into a string
rejoined = " - ".join(fields)
print(rejoined)
# Practical: reformat comma-separated data as tab-separated
tab_separated = "\t".join(fields)
print(tab_separated)
The .split() method is your go-to tool for breaking structured text into usable pieces. When called with no argument, it splits on whitespace (spaces, tabs, newlines). When you pass a delimiter like ",", it splits on that exact character. The .join() method works the other way round — the string you call it on becomes the separator between each item in the list.
"red;green;blue;yellow" on the semicolon character. What happens if you split "Hello" on the empty string list("Hello")? Use ", ".join(["apples", "bananas", "cherries"]) to create a neat comma-separated list.
Example 5: String Traversal with Loops
Because strings are sequences, you can traverse (loop through) them character by character using a for loop. This is incredibly useful when you need to examine or transform individual characters — for example, building up a new modified string one piece at a time:
# Simple traversal: print each character
word = "Python"
for char in word:
print(char, end=" ")
print() # New line
# Building a new string: remove all vowels
original = "String Manipulation"
result = ""
for char in original:
if char.lower() not in "aeiou":
result = result + char
print("Without vowels:", result)
# Building a new string: add a dash between each character
spaced = ""
for i in range(len(original)):
spaced = spaced + original[i]
if i < len(original) - 1:
spaced = spaced + "-"
print("Dashed:", spaced)
# Count different character types
text = "Hello World 123!"
letters = 0
digits = 0
spaces = 0
other = 0
for char in text:
if char.isalpha():
letters += 1
elif char.isdigit():
digits += 1
elif char == " ":
spaces += 1
else:
other += 1
print(f"Letters: {letters}, Digits: {digits}, Spaces: {spaces}, Other: {other}")
The key technique here is building up a new string. Because strings are immutable (you cannot change them in place), we start with an empty string "" and add characters to it one at a time using concatenation (+). This pattern — loop, check, build — is one of the most common in all of programming.
"hello" becomes "hElLo". Hint: use range(len(text)) and check whether the index is odd or even with i % 2.
Example 6: Practical — Email Validator
Let us put several string techniques together to build a practical email address validator. A valid email needs certain features: it must contain an @ symbol, have text before and after the @, and end with a recognised domain. This is the kind of validation that happens every time you sign up for a website:
def validate_email(email):
"""Check if an email address is valid."""
# Remove any accidental whitespace
email = email.strip()
# Check 1: Must contain exactly one @ symbol
if email.count("@") != 1:
return "Invalid: must contain exactly one @ symbol"
# Check 2: Split into local part and domain
parts = email.split("@")
local = parts[0]
domain = parts[1]
# Check 3: Local part must not be empty
if len(local) == 0:
return "Invalid: nothing before the @ symbol"
# Check 4: Domain must not be empty
if len(domain) == 0:
return "Invalid: nothing after the @ symbol"
# Check 5: Domain must contain a dot
if "." not in domain:
return "Invalid: domain must contain a dot (e.g. .com, .co.uk)"
# Check 6: Must end with a valid domain extension
valid_endings = [".com", ".org", ".co.uk", ".net", ".edu", ".ac.uk"]
has_valid_ending = False
for ending in valid_endings:
if email.endswith(ending):
has_valid_ending = True
if not has_valid_ending:
return "Invalid: unrecognised domain extension"
return "Valid email address"
# Test with different email addresses
test_emails = [
"student@school.co.uk",
"alice@gmail.com",
"missing-at-sign.com",
"@nodomain.com",
"user@",
"double@@at.com",
"bob@website.xyz"
]
for email in test_emails:
result = validate_email(email)
print(f"{email:30s} -> {result}")
This example combines many of the skills from this chapter: .strip() to clean input, .count() to check for the right number of @ symbols, .split() to separate the parts, len() to check for empty sections, in to check for a dot, and .endswith() to validate the domain. Real-world email validation is even more complex, but this covers the essentials.
How Does It Work?
Strings Are Sequences
A string in Python is a sequence of characters, much like a list is a sequence of items. Each character has a numbered position (an index), starting at 0 for the first character. This means you can access any character by its position, loop through every character one at a time, and use slicing to extract sections.
For the string "Python", the indices look like this:
| Character | P | y | t | h | o | n |
|---|---|---|---|---|---|---|
| Index | 0 | 1 | 2 | 3 | 4 | 5 |
| Negative index | -6 | -5 | -4 | -3 | -2 | -1 |
Negative indices count backwards from the end. So "Python"[-1] gives "n" (the last character), and "Python"[-2] gives "o".
Indexing and Slicing Syntax
The full slicing syntax is string[start:stop:step]:
- start — the index where the slice begins (included). Defaults to 0 if omitted.
- stop — the index where the slice ends (not included). Defaults to the end of the string if omitted.
- step — how many characters to skip. Defaults to 1. A step of
-1reverses the string.
Some useful examples:
| Expression | Result | Explanation |
|---|---|---|
"Python"[0:3] |
"Pyt" |
Characters at index 0, 1, 2 |
"Python"[2:] |
"thon" |
From index 2 to the end |
"Python"[:4] |
"Pyth" |
From the start up to index 4 |
"Python"[::2] |
"Pto" |
Every second character |
"Python"[::-1] |
"nohtyP" |
The whole string reversed |
Strings Are Immutable
One of the most important things to understand about strings is that they are immutable — you cannot change them after they are created. When you call a method like .upper() or .replace(), Python does not modify the original string. Instead, it creates and returns a brand new string. If you want to keep the result, you must store it in a variable:
greeting = "hello"
# This does NOT change greeting
greeting.upper()
print(greeting) # Still "hello"!
# You must save the result
greeting = greeting.upper()
print(greeting) # Now "HELLO"
name = name.upper(). This is one of the most common mistakes beginners make!
Common String Methods Summary
| Method | What It Does | Example |
|---|---|---|
.upper() |
Returns the string in uppercase | "hi".upper() → "HI" |
.lower() |
Returns the string in lowercase | "HI".lower() → "hi" |
.title() |
Capitalises the first letter of each word | "hi there".title() → "Hi There" |
.strip() |
Removes leading and trailing whitespace | " hi ".strip() → "hi" |
.replace(old, new) |
Replaces all occurrences of old with new | "hello".replace("l", "r") → "herro" |
.count(sub) |
Counts how many times sub appears | "banana".count("a") → 3 |
.find(sub) |
Returns the index of the first occurrence, or -1 |
"hello".find("ll") → 2 |
.split(sep) |
Splits the string into a list at each sep | "a,b,c".split(",") → ["a","b","c"] |
.join(list) |
Joins a list of strings using the string as separator | "-".join(["a","b"]) → "a-b" |
.startswith(s) |
Checks if the string begins with s | "hello".startswith("he") → True |
.endswith(s) |
Checks if the string ends with s | "hello".endswith("lo") → True |
.isdigit() |
Returns True if all characters are digits |
"42".isdigit() → True |
.isalpha() |
Returns True if all characters are letters |
"hi".isalpha() → True |
.strip(). Almost every program that reads user input or data from a file uses it to remove accidental whitespace. When a user types " hello " into a form, .strip() cleans it up to just "hello". There are also .lstrip() and .rstrip() for removing whitespace from only the left or right side.
ASCII and Character Codes
Computers do not actually store letters — they store numbers. Every character you see on screen has a corresponding numeric code. The original system for this is called ASCII (American Standard Code for Information Interchange), which assigns a number from 0 to 127 to each character. For example:
| Character | ASCII Code | Character | ASCII Code |
|---|---|---|---|
A |
65 | a |
97 |
B |
66 | b |
98 |
Z |
90 | z |
122 |
0 |
48 | 9 |
57 |
(space) |
32 | ! |
33 |
Python gives you two built-in functions to work with character codes:
ord(character)— returns the numeric code for a character. For example,ord("A")returns65.chr(number)— returns the character for a numeric code. For example,chr(65)returns"A".
# Convert characters to their ASCII codes
print("A =", ord("A"))
print("a =", ord("a"))
print("0 =", ord("0"))
print("! =", ord("!"))
# Convert ASCII codes back to characters
print("65 =", chr(65))
print("97 =", chr(97))
print("48 =", chr(48))
# This is why alphabetical sorting works!
# 'A' (65) < 'B' (66) < 'C' (67) ...
print("Is 'A' less than 'B'?", "A" < "B") # True
print("Is 'a' less than 'Z'?", "a" < "Z") # False! (97 > 90)
# Print the entire uppercase alphabet using chr()
alphabet = ""
for code in range(65, 91):
alphabet += chr(code)
print("Alphabet:", alphabet)
Understanding character codes explains why string comparison works alphabetically. When Python compares "apple" < "banana", it compares the ASCII codes character by character. Since ord("a") is 97 and ord("b") is 98, "apple" comes first. However, be careful: uppercase letters (65–90) have lower codes than lowercase letters (97–122), so "Z" < "a" is True! This is why you often need to convert to the same case before comparing.
ord("🐍") returns 128013 and len("Hello 🌎") counts the globe emoji as a single character. Unicode is a superset of ASCII — the first 128 Unicode codes are identical to ASCII.
"Hello こんにちは مرحبا 👋" is a perfectly valid Python string. Each character has a unique code point — ord("A") returns 65, but ord("こ") returns 12371!
.replace() changes the original string. It does not. For example, after running name = "Alice" followed by name.replace("A", "a"), the variable name is still "Alice". You must write name = name.replace("A", "a") to save the change.
"Hello" is "Hello"[0], which is "H". Trying to access an index beyond the length of the string (e.g. "Hello"[10]) will cause an IndexError.
Worked Example: Building a Caesar Cipher
The Problem
A Caesar cipher is one of the oldest encryption techniques, supposedly used by Julius Caesar to send secret messages. It works by shifting each letter in the message by a fixed number of positions in the alphabet. For example, with a shift of 3:
AbecomesDBbecomesEXbecomesA(it wraps around!)ZbecomesC
This is a classic GCSE exam question. Let us build it step by step using ord(), chr(), and modular arithmetic.
Uppercase letters run from ord("A") = 65 to ord("Z") = 90. To shift a letter, we need to convert it to a number (0–25), add the shift, wrap around using the modulo operator (%), and convert back to a letter.
To encrypt the letter "H" with a shift of 3:
- Get the ASCII code:
ord("H") = 72 - Subtract 65 to get a position (0–25):
72 - 65 = 7 - Add the shift:
7 + 3 = 10 - Use modulo to wrap around:
10 % 26 = 10 - Add 65 back to get the ASCII code:
10 + 65 = 75 - Convert back to a character:
chr(75) = "K"
So "H" becomes "K".
The modulo operator (%) is the key to wrapping. For example, "Y" with a shift of 3:
- Position:
ord("Y") - 65 = 24 - Shifted:
24 + 3 = 27 - Wrapped:
27 % 26 = 1 - Result:
chr(1 + 65) = "B"
Without the modulo, we would get code 92 which is not a letter!
We loop through each character, shift letters, and leave non-letter characters (spaces, punctuation) unchanged.
def caesar_cipher(message, shift):
"""Encrypt a message using a Caesar cipher."""
result = ""
for char in message:
if char.isalpha():
# Determine if uppercase or lowercase
if char.isupper():
base = ord("A")
else:
base = ord("a")
# Shift the character
position = ord(char) - base # 0-25
shifted = (position + shift) % 26 # Wrap around
new_char = chr(shifted + base) # Back to a letter
result += new_char
else:
# Keep spaces, punctuation, digits unchanged
result += char
return result
# Encrypt a message with a shift of 3
original = "HELLO WORLD"
encrypted = caesar_cipher(original, 3)
print("Original: ", original)
print("Encrypted:", encrypted)
# Decrypt by shifting in the opposite direction
decrypted = caesar_cipher(encrypted, -3)
print("Decrypted:", decrypted)
# Try with mixed case and punctuation
secret = "Attack at dawn!"
coded = caesar_cipher(secret, 5)
print("\nOriginal: ", secret)
print("Encrypted:", coded)
print("Decrypted:", caesar_cipher(coded, -5))
%) gives you the remainder after division. 27 % 26 = 1 because 27 divided by 26 leaves a remainder of 1. This is perfect for “wrapping around” — when you go past Z, you loop back to A. You will encounter modular arithmetic in many computing contexts, including hash functions, clock arithmetic, and cryptography.
Real-World Applications
Where Is String Manipulation Used?
Data Cleaning
When organisations collect data from forms, surveys, or imports, it is often messy. Names might be typed as " jOHN sMITH " instead of "John Smith". Data cleaning scripts use .strip() to remove extra whitespace, .title() or .capitalize() to fix capitalisation, and .replace() to standardise formats. Databases with millions of records rely on these operations to keep data consistent.
Natural Language Processing (NLP)
NLP is the field of computer science that teaches computers to understand human language. Applications like voice assistants (Siri, Alexa), translation services, and chatbots all start by breaking text into words using .split(), converting to lowercase with .lower(), and removing punctuation. Sentiment analysis programs use these techniques to determine whether a product review is positive or negative by checking for key words.
Cybersecurity
One of the most common attacks on websites is injection — where an attacker types malicious code into a form field hoping it will be executed. String validation is the first line of defence. Checking that usernames contain only safe characters, that inputs do not contain <script> tags, and that data matches expected patterns (using methods like .isalnum(), .find(), and in) helps prevent these attacks.
File Processing
Programs that read data files — especially CSV (Comma-Separated Values) files — rely heavily on .split(",") to break each line into fields, .strip() to clean up whitespace, and type conversion to turn string values into numbers. Scientific research, business reporting, and government statistics all depend on these string-processing techniques.
Search Engines
When you type a query into Google, the search engine tokenises your input — splitting it into individual words using .split(), converting to lowercase, removing common words like “the” and “is” (called stop words), and sometimes stemming words (reducing “running”, “runs”, and “ran” to the root word “run”). All of this is fundamentally string manipulation.
Common Mistakes to Avoid
Watch Out for These Pitfalls
1. Forgetting that strings are immutable
This is the number one mistake beginners make. String methods return new strings — they do not change the original. If you write name.upper() without saving the result, nothing changes:
name = "alice"
name.upper() # This does NOTHING to name!
name = name.upper() # This is correct
2. Off-by-one errors in slicing
Remember that the stop index in a slice is not included. If you want characters at indices 0, 1, 2, 3, and 4, you write text[0:5], not text[0:4]. A useful rule: the number of characters in a slice is stop - start.
3. Case sensitivity when comparing strings
In Python, "Hello" and "hello" are not the same string. If you are comparing user input, always convert both sides to the same case first:
if user_input.lower() == "yes": # Handles "Yes", "YES", "yes", etc.
4. Confusing .find() returning -1 with an error
When .find() cannot locate the substring, it returns -1 — it does not raise an error. This can cause bugs if you use the result as an index without checking. Always test the return value:
pos = text.find("hello")
if pos != -1: # Found it!
print("Found at index", pos)
If you want Python to raise an error when the substring is not found, use .index() instead of .find().
Key Vocabulary
| Term | Definition |
|---|---|
| String | A sequence of characters (text data), written in quotes in Python. For example, "Hello". |
| Character | A single letter, digit, symbol, or space. In Python, a character is simply a string of length 1. |
| Index | The position number of a character within a string. Python uses zero-based indexing (the first character is at index 0). |
| Slice | A portion of a string extracted using the [start:stop:step] syntax. |
| Substring | A smaller string that appears within a larger string. For example, "ell" is a substring of "Hello". |
| Immutable | Cannot be changed after creation. Strings in Python are immutable — methods return new strings rather than modifying the original. |
| Concatenation | Joining two or more strings together using the + operator. For example, "Hello" + " " + "World". |
| Method | A function that belongs to an object, called using dot notation. For example, "hello".upper() calls the upper method on the string. |
| Traverse | To iterate (loop) through a sequence one element at a time. For example, for char in "hello": traverses each character. |
| ASCII | American Standard Code for Information Interchange — a system that assigns a numeric code (0–127) to each character. ord("A") returns 65. |
| Unicode | A modern character encoding standard that covers over 149,000 characters from all the world's writing systems. Python uses Unicode internally. |
| Delimiter | A character or string used to separate pieces of data. In CSV files, the delimiter is a comma. In .split(","), the comma is the delimiter. |
| split() | A string method that breaks a string into a list of substrings at each occurrence of a delimiter. |
| join() | A string method that concatenates a list of strings, inserting the string between each element. |
| strip() | A string method that removes leading and trailing whitespace (or specified characters) from a string. |
Your Turn
Take the sentence below and print it in three different forms: uppercase, lowercase, and title case. The code is mostly written for you — just fill in the blanks with the correct string methods.
STEP 2: Print the sentence converted to uppercase
STEP 3: Print the sentence converted to lowercase
STEP 4: Print the sentence converted to title case
sentence = "the quick brown fox jumps over the lazy dog"
# Print in uppercase (replace ____ with the correct method)
print(sentence.____())
# Print in lowercase
print(sentence.____())
# Print in title case
print(sentence.____())
The three methods you need are: .upper() for all capitals, .lower() for all lowercase, and .title() for capitalising the first letter of each word.
Your completed code should look like:
print(sentence.upper())
print(sentence.lower())
print(sentence.title())
Write a program that counts how many vowels (a, e, i, o, u) are in a given word. You will need to loop through each character and check whether it is a vowel.
STEP 2: Convert the word to lowercase (so “A” and “a” both count)
STEP 3: Create a counter variable, starting at 0
STEP 4: Loop through each character in the word
STEP 5: If the character is in “aeiou”, add 1 to the counter
STEP 6: Print the total number of vowels
word = "Programming"
word_lower = word.lower()
count = 0
for char in word_lower:
if char in "aeiou":
# Add 1 to the count
pass # Replace this line
print("The word '" + word + "' has", count, "vowels.")
Replace the pass line with count = count + 1 (or the shorthand count += 1). This increases the counter by 1 each time a vowel is found.
The word “Programming” should have 3 vowels: o, a, i.
Try changing the word to your own name or a longer sentence and see if the count is correct!
Create a simple text processor that performs at least two of the following tasks on a sentence of your choice. Be creative!
- Word censor: Replace a specific word with asterisks (e.g. replace “bad” with “***”)
- String reverser: Print the sentence backwards
- Acronym maker: Take the first letter of each word to form an acronym (e.g. “as soon as possible” → “ASAP”)
- Character report: Count letters, digits, and spaces separately
STEP 2: Choose at least two of the tasks above
STEP 3: Use string methods, slicing, and/or loops to complete each task
STEP 4: Print the results with clear labels so the output is easy to read
# Text Processor
# Choose a sentence and perform at least two tasks on it
sentence = "Python Is Super Cool"
# Task 1: Censor a word
# (replace a word with asterisks)
# Task 2: Reverse the string
# (use slicing to reverse it)
# Task 3: Create an acronym
# (take the first letter of each word)
# Print your results!
Here are some approaches for each task:
- Censor: Use
sentence.replace("Cool", "****") - Reverse: Use
sentence[::-1] - Acronym: Use
sentence.split()to get a list of words, then loop through them and takeword[0]from each. Build up the acronym by adding each first letter to an empty string. - Character report: Loop through each character and use
.isalpha(),.isdigit(), and check for" "to count each type.
Here is an example that does all three:
censored = sentence.replace("Cool", "****")
reversed_text = sentence[::-1]
acronym = ""
for word in sentence.split():
acronym = acronym + word[0]
print("Censored:", censored)
print("Reversed:", reversed_text)
print("Acronym:", acronym.upper())
A palindrome is a word or phrase that reads the same forwards and backwards. For example, “racecar”, “madam”, and “level” are all palindromes. Write a program that checks whether a word is a palindrome.
STEP 2: Convert the word to lowercase (so “Racecar” and “racecar” both work)
STEP 3: Reverse the lowercase word using slicing
STEP 4: Compare the lowercase word to its reverse
STEP 5: If they are the same, print that it is a palindrome
STEP 6: Otherwise, print that it is not a palindrome
# Palindrome Checker
word = "Racecar"
# Step 1: Convert to lowercase
word_lower = word.lower()
# Step 2: Reverse the string (use slicing!)
reversed_word = ____ # Replace ____ with the reversed version
# Step 3: Compare and print the result
if word_lower == reversed_word:
print(f"'{word}' is a palindrome!")
else:
print(f"'{word}' is NOT a palindrome.")
# Bonus: Test with several words
test_words = ["madam", "python", "level", "hello", "kayak"]
for w in test_words:
if w == w[::-1]:
print(f" {w} -> palindrome")
else:
print(f" {w} -> not a palindrome")
Replace ____ with word_lower[::-1]. This uses Python’s slice syntax with a step of -1 to reverse the string.
Challenge extension: Can you make the palindrome checker work for phrases too? For example, “A man a plan a canal Panama” is a palindrome if you ignore spaces and capitalisation. Hint: use .replace(" ", "") to remove spaces before checking.
Write a program that checks the strength of a password. A strong password should meet all of the following criteria:
- At least 8 characters long
- Contains at least one uppercase letter
- Contains at least one lowercase letter
- Contains at least one digit
- Contains at least one special character (e.g.
!@#$%^&*)
STEP 2: Create boolean variables for each check, initially set to False
STEP 3: Check the length is at least 8
STEP 4: Loop through each character in the password
STEP 5: For each character, check if it is uppercase, lowercase, a digit, or special
STEP 6: After the loop, report which criteria are met and which are missing
STEP 7: If all criteria are met, print that the password is strong
# Password Strength Checker
password = "MyP@ssw0rd"
# Define the special characters to check for
special_chars = "!@#$%^&*()-_=+[]{}|;:',.<>?/"
# Track which criteria are met
has_length = len(password) >= 8
has_upper = False
has_lower = False
has_digit = False
has_special = False
# Loop through each character and check
for char in password:
# Add your checks here
pass
# Report results
print(f"Password: {password}")
print(f" Length >= 8: {has_length}")
print(f" Has uppercase: {has_upper}")
print(f" Has lowercase: {has_lower}")
print(f" Has digit: {has_digit}")
print(f" Has special char: {has_special}")
if has_length and has_upper and has_lower and has_digit and has_special:
print("\nPassword is STRONG!")
else:
print("\nPassword is WEAK. Please fix the issues above.")
Replace the pass line inside the loop with checks for each character type:
if char.isupper():
has_upper = True
elif char.islower():
has_lower = True
elif char.isdigit():
has_digit = True
elif char in special_chars:
has_special = True
Test with passwords like "hello" (too weak), "Hello123" (missing special character), and "MyP@ssw0rd" (strong).
Chapter Summary
What You Have Learnt
- Strings are sequences of characters, accessed by index starting from 0. Negative indices count from the end.
- Slicing uses the syntax
[start:stop:step]to extract portions of a string. The stop index is not included. - Strings are immutable — methods like
.upper(),.replace(), and.strip()return new strings; they never modify the original. - String methods let you change case (
.upper(),.lower(),.title()), search (.find(),.count(),in), check properties (.isdigit(),.isalpha(),.startswith()), and clean text (.strip(),.replace()). - split() and join() convert between strings and lists — essential for processing structured data like CSV.
- String traversal using
for char in string:lets you examine or transform each character individually. - ASCII and Unicode assign numeric codes to characters. The
ord()andchr()functions convert between characters and their codes. - The Caesar cipher demonstrates how
ord(),chr(), and modular arithmetic can be used for encryption — a classic GCSE topic. - Practical applications include email validation, password checking, data cleaning, and text analysis.
Think About It
Take a moment to think about what you have learnt in this chapter. Try to answer these questions in your head or discuss them with a classmate:
- Why does Python start counting string indices at 0 instead of 1? Can you think of any advantages this might have?
- What does it mean for strings to be immutable? Why do you think Python was designed this way?
- If you wanted to check whether a user’s input was a valid email address, which string methods might you use? (Think about what an email address looks like.)
- Why is it useful to convert a string to lowercase before searching for something inside it?
- Can you think of a real-world app or website that must use string manipulation behind the scenes? What might it be doing with the text?
- How does knowing about ASCII codes help you understand why
"apple" < "banana"isTruein Python? - Why is the modulo operator (
%) essential for making the Caesar cipher work correctly?
Exam-Style Questions
Practise answering these questions as you would in a GCSE Computer Science exam.
Question 1: What does it mean for strings to be ‘immutable’ in Python? Give an example to illustrate your answer.
Question 2: Explain the difference between .find() and the in keyword when searching for text within a string. When might you choose one over the other?
Question 3: Write a Python function called count_types that takes a string as a parameter and returns the number of uppercase letters, lowercase letters, and digits it contains. For example, count_types("Hello World 123") should print: Uppercase: 2, Lowercase: 8, Digits: 3.
Question 1 (2 marks): Immutable means the string cannot be changed after it has been created (1 mark). For example, calling name.upper() does not change name — it returns a new string, so you must write name = name.upper() to store the result (1 mark).
Question 2 (2 marks): The in keyword returns True or False to indicate whether the substring exists (1 mark). The .find() method returns the index (position) where the substring was found, or -1 if it was not found (1 mark). You would use in when you only need to know if the text is present, and .find() when you need to know where it is.
Question 3 (4 marks):
def count_types(text): (1 mark for function definition with parameter)
upper = 0
lower = 0
digits = 0 (1 mark for initialising counters)
for char in text: (1 mark for loop and correct checks)
if char.isupper():
upper += 1
elif char.islower():
lower += 1
elif char.isdigit():
digits += 1
print(f"Uppercase: {upper}, Lowercase: {lower}, Digits: {digits}") (1 mark for correct output)
Extra Resources
Keep practising your string skills:
- GCSE Topic 6: Data Types — String operations, concatenation, slicing, and traversal with interactive code editors
- Python Error Spotter — Spot errors in Python code including common string mistakes
- BBC Bitesize Edexcel Computer Science — Comprehensive GCSE revision including string handling and data types
- W3Schools Python Strings — Interactive tutorials covering all the basics of Python strings
- W3Schools Python String Methods — A complete reference of every Python string method with examples
What’s Next?
No matter how carefully you write your code, things can go wrong: a user might type a word when you expected a number, a file might not exist, or a calculation might try to divide by zero. In Chapter 10: Error Handling, you will learn how to anticipate these problems and handle them gracefully using try and except blocks — so your programs do not just crash but instead respond helpfully when something unexpected happens.