Python Foundations

String Manipulation

Strings are everywhere in Paper 4 — IDs, names, validation, file records. This page rebuilds string handling from scratch: what a string is, indexing and slicing, the off-by-one MID rule, manual counting and searching, reversing, palindrome checks, and random.sample() — the Cambridge way, without leaning on built-ins the mark scheme wants you to write yourself.

7.1 String Basics

A string is an ordered sequence of characters. The characters can be letters, digits, symbols, or spaces — whatever you can type on a keyboard. Python lets you write a string between single or double quotes. Both produce exactly the same string; choose whichever quote does not appear inside your text.

The string "Cambridge" stored in the variable word has 9 characters. Each character has a fixed position (called an index), starting at 0. So C is at index 0, a at index 1, all the way to e at index 8.

# Source: moshikur.com | Cambridge A Level CS 9618
greeting = "Hello"
language = 'Python'
fullName = "Md. Moshikur Rahman"
emptyStr = ""             # a valid string with zero characters
mixed = "Room 204, Lab-3"
Key rule — strings are immutable:
  • Once a string exists, you cannot change a character inside it.
  • word[0] = "B" always raises a TypeError.
  • To "change" a string, you must build a new string and assign it back to the variable.
  • This is not a quirk of Python — most exam languages enforce it.
  • Every Paper 4 task that says "modify the string" really means "build a new one and replace the old one."

Once you internalise that, the code structure becomes the same every time: declare an empty newString = "", loop, append, output.

Task — Output and immutability
A student writes the following Python code. State the output and explain what would happen if the second line were uncommented. word = "Cat" # word[0] = "B" print(word)
Hint:
  • Read the assignment: word stores "Cat"
  • The middle line begins with # so Python ignores it
  • print(word) outputs the string unchanged
  • If uncommented, word[0] = "B" raises TypeError (strings are immutable)
Your Turn — Predict the output and the error
Predict the output of this code, and explain what error appears if you try to write name[2] = "X" on the last line. name = "Sara" print(name) # name[2] = "X"
Hint:
  • First work out what name stores
  • print(name) outputs the string unchanged
  • Think about whether Python lets you change one character in place
Task — Concatenation: + vs print comma
State the output of the program below. a = "Lab" b = "204" print(a + "-" + b) print(a, b)
Hint:
  • The + operator joins strings end-to-end with no extra spaces
  • The literal "-" is inserted between a and b
  • print(a, b) separates arguments with a single space by default
Your Turn — Concatenation: + vs print comma
State the output of this program. room = "Room" num = "12" print(room + num) print(room, num)
Hint:
  • + inserts nothing between the two strings
  • print(room, num) inserts a space by default
Task — Build a new string with slice-and-concatenate
Write a single Python statement that produces the string "Bat" from word = "Cat" without using item assignment. State the value of the new string.
Hint:
  • The only character to change is at index 0
  • Keep characters from index 1 onwards using word[1:]
  • Concatenate "B" in front of the slice
Your Turn — Build a new string with slice-and-concatenate
Starting with word = "Dog", write a single Python statement that produces "Log" without using item assignment. State the new value.
Hint:
  • Index 0 holds "D" — that is the character to change
  • word[1:] gives "og"
  • Concatenate "L" with that slice
Exam tip:
  • In Paper 4 mark schemes, "modify a string" never means item assignment.
  • The examiner expects to see an empty string built up character by character, or a slice-and-concatenate expression.
  • Writing s[i] = c earns zero marks even if the rest of the logic is correct.

7.2 Indexing & Slicing

Each character in a Python string has a position called an index. The first character is at index 0, the second at 1, and so on. Python also accepts negative indexes that count backwards from the end: -1 is the last character, -2 the second-last.

# Source: moshikur.com | Cambridge A Level CS 9618
word = "Cambridge"
print(word[0])   # C  — first character
print(word[4])   # r
print(word[-1])  # e  — last character
print(word[-3])  # d  — third from the end
Key rule — slicing notation:
  • s[start:end] returns the characters from position start up to but not including position end.
  • Either may be omitted: s[:3] means "from the beginning to index 3", s[5:] means "from index 5 to the end".
  • The slice length is always end - start.

The fact that the end index is excluded looks strange at first, but it has a useful side-effect: the number of characters in the slice is always end - start. So word[2:5] always gives 3 characters, no matter what word is.

Pseudocode MID vs Python slicing — the off-by-one rule

The pseudocode function MID(string, x, y) returns y characters starting at position x, where x is counted from 1. Python slicing counts from 0. The conversion is straightforward but easy to get wrong under exam pressure.

MID(s, x, y) → s[x − 1 : x − 1 + y] — subtract 1 from the start, then add the length to find the end. So MID("ABCDEFGH", 2, 3) picks positions 2, 3, 4 in pseudocode (B, C, D) — in Python this is the slice s[1:4], which returns "BCD".

PseudocodePython equivalentResult for "ABCDEFGH"
MID(s, 1, 3)s[0:3]"ABC"
MID(s, 2, 3)s[1:4]"BCD"
MID(s, 4, 2)s[3:5]"DE"
LEFT(s, 3)s[0:3] or s[:3]"ABC"
RIGHT(s, 3)s[-3:]"FGH"
Task — Translate MID to Python slicing
The pseudocode call MID("ALGORITHM", 4, 5) is required in a Python program. Write a single Python expression that returns the same value, and state the result.
Hint:
  • Identify the pseudocode position: 5 characters starting at position 4
  • Position 4 in pseudocode is the 4th character (O)
  • Convert to Python: position 4 becomes index 3
  • Slice end = 3 + 5 = 8
Your Turn — Translate MID to Python slicing
Write a single Python expression equivalent to MID("COMPUTING", 3, 4) and state the result.
Hint:
  • Convert position 3 to a Python index (3 - 1 = 2)
  • 4 characters, so slice end = 2 + 4 = 6
  • Indexes 2, 3, 4, 5 of "COMPUTING" are M, P, U, T
Task — Indexing positive and negative
Given word = "PYTHON", state the value of each expression. (i) word[0] (ii) word[-1] (iii) word[2] (iv) word[-3]
Hint:
  • word[0] is the first character
  • word[-1] is the last character
  • word[2]: P=0, Y=1, T=2
  • word[-3]: H=-3, O=-2, N=-1
Your Turn — Indexing positive and negative
Given word = "SCHOOL", state the value of each expression. • word[0] • word[-1] • word[3] • word[-4]
Hint:
  • Draw the index row twice: positive numbers above, negative below
  • S=0, C=1, H=2, O=3, O=4, L=5
  • S=-6, C=-5, H=-4, O=-3, O=-2, L=-1
Task — Slicing s[start:end]
Given s = "COMPUTER", state the result of each slice. (i) s[:4] (ii) s[4:] (iii) s[2:6] (iv) s[-3:]
Hint:
  • s[:4] — from start, up to (not including) index 4
  • s[4:] — from index 4 to the end
  • s[2:6] — indexes 2, 3, 4, 5
  • s[-3:] — last three characters
Your Turn — Slicing s[start:end]
Given s = "BANGLADESH", state the result of each slice. • s[:4] • s[6:] • s[3:7] • s[-4:]
Hint:
  • Count the included characters: slice length is always end - start
  • B=0, A=1, N=2, G=3, L=4, A=5, D=6, E=7, S=8, H=9
Exam tip:
  • When converting MID(s, x, y) to Python, always write the slice as s[x-1 : x-1+y].
  • Memorise that pattern.
  • The most common Paper 4 mistake is writing s[x:x+y], which is off by one and silently returns the wrong substring — no error, just wrong marks.

7.3 Length Without len()

Examiners sometimes design Paper 4 questions that expect you to demonstrate understanding, not just knowledge of built-ins. When a question says "write a function that returns the number of characters in a string", you score full marks by showing the loop-and-count logic. Quietly using len() can lose method marks because the mark scheme rewards each line of the manual algorithm.

You count characters in a string the same way you count anything else in programming: set a counter to zero, walk through every item, and add one each time. With a string, walking through means a for loop over each character.

# Source: moshikur.com | Cambridge A Level CS 9618
word = "Cambridge"
count = 0
for ch in word:
    count = count + 1
print("Length:", count)  # Length: 9
Key rule — the counter pattern: Every Cambridge "count something in a string" question follows the same three-line pattern:
  • Set a counter variable to 0.
  • Loop through every character.
  • Inside the loop, increase the counter when the condition is met (here, the condition is "always true").

You can also write the loop using an explicit index, which is closer to the Cambridge pseudocode style. Both are correct, but the index version is often easier to extend later when you need positions, not just counts.

# Source: moshikur.com | Cambridge A Level CS 9618
word = "Cambridge"
count = 0
i = 0
while i < len(word):   # if len() is allowed
    count = count + 1
    i = i + 1
print("Length:", count)

If the question forbids len() entirely, the simpler approach is to use the for ch in word loop above, which knows when to stop without needing the length in advance.

Task — countChars(s) without len()
Write a Python function countChars(s) that returns the number of characters in s without using len(). Show a test call and the expected output.
Hint:
  • Define the function with one parameter s
  • Initialise a counter: count = 0 before the loop
  • Iterate using for ch in s — works without knowing the length
  • Return the result and test it
Your Turn — countVowels(s) without len()
Write a Python function countVowels(s) that returns the number of vowels (a, e, i, o, u — in either case) in s, without using len(). Test it on "Education".
Hint:
  • Extend the counter pattern by adding an if inside the loop
  • Convert the character to lower case before checking
  • Test if the lower-cased character is in the string "aeiou"
  • Return count at the end
Task — countSpaces(s)
Write a Python function countSpaces(s) that returns the number of space characters in s. Test it on "Hello World from CS".
Hint:
  • Counter pattern: start at 0
  • Loop and compare each character to " "
  • Return the counter
Your Turn — countCommas(s)
Write a Python function countCommas(s) that returns the number of comma characters in s. Test it on "red,green,blue,yellow".
Hint:
  • Same counter pattern as countSpaces
  • Change the comparison character to ","
Task — countUpper(s) without str.isupper()
Write a Python function countUpper(s) that returns the number of upper-case letters (A–Z) in s, without using str.isupper(). Test it on "Computer Science 9618".
Hint:
  • Counter pattern
  • Compare each character with the range "A" to "Z" using ASCII ordering
  • Use ch >= "A" and ch <= "Z"
Your Turn — countLower(s) without str.islower()
Write a Python function countLower(s) that returns the number of lower-case letters (a–z) in s, without using str.islower(). Test it on "My Name Is Sara".
Hint:
  • Same loop as countUpper
  • Compare each character against "a" and "z"
Exam tip:
  • When a Paper 4 task says &quot;write code to find the length / count / total&quot; it nearly always wants the algorithm shown, not the built-in.
  • Show the counter, the loop, and the increment — those are the marks.
  • Reserve len() for cases where the question explicitly allows it or uses it incidentally inside another task.

7.4 Pseudocode Functions in Python

Cambridge mark schemes and pseudocode inserts use a fixed set of string functions — LEFT, RIGHT, MID, LENGTH, TO_UPPER, TO_LOWER. The exam will give you the pseudocode and tell you to write the program in Python. Knowing the one-to-one Python equivalent for each call is the difference between answering in two minutes and panicking through a translation.

Pseudocode callPython equivalentExample
LENGTH(s)len(s)len("Happy Days") → 10
LEFT(s, n)s[0:n] or s[:n]"ABCDEFGH"[:3] → "ABC"
RIGHT(s, n)s[-n:]"ABCDEFGH"[-3:] → "FGH"
MID(s, x, y)s[x-1 : x-1+y]"ABCDEFGH"[1:4] → "BCD"
TO_UPPER(s)s.upper()"Error 803".upper() → "ERROR 803"
TO_LOWER(s)s.lower()"JIM 803".lower() → "jim 803"
UCASE(ch)ch.upper()"h".upper() → "H"
LCASE(ch)ch.lower()"W".lower() → "w"
str1 & str2str1 + str2"Summer" + " " + "Pudding"
NUM_TO_STR(n)str(n)str(87.5) → "87.5"
STR_TO_NUM(s)int(s) or float(s)float("23.45") → 23.45
Key rule — the off-by-one rule:
  • When converting MID(s, x, y) to Python, subtract 1 from the start, then add the length to find the end: MID(s, x, y) → s[x-1 : x-1+y].
  • This is the single most-tested translation in Paper 4 string questions.
Task — Translate pseudocode to Python
A pseudocode algorithm is given. Re-implement it in Python and state the output. // Pseudocode word <- "INFORMATION" part1 <- LEFT(word, 3) part2 <- MID(word, 5, 4) part3 <- RIGHT(word, 2) result <- part1 & "-" & part2 & "-" & TO_UPPER(part3) OUTPUT result
Hint:
  • Translate LEFT: LEFT(word, 3) → word[:3] → "INF"
  • Translate MID: MID(word, 5, 4) → word[5-1 : 5-1+4] → word[4:8] → "RMAT"
  • Translate RIGHT: RIGHT(word, 2) → word[-2:] → "ON"
  • Replace & with +; replace TO_UPPER with .upper()
Your Turn — Translate pseudocode to Python
Translate this pseudocode into Python and state the output. name <- "ProgrammingLanguage" a <- LEFT(name, 4) b <- MID(name, 12, 4) c <- RIGHT(name, 4) output a & "/" & TO_LOWER(b) & "/" & c
Hint:
  • Apply the off-by-one rule for MID
  • b is 4 characters starting at position 12
  • LEFT(name, 4) → name[:4] → "Prog"
  • RIGHT(name, 4) → name[-4:] → "uage"
Task — LEFT/RIGHT/TO_UPPER translation
Translate the pseudocode to Python and state the output. code <- "BANGLADESH" out <- LEFT(code, 4) & "-" & RIGHT(code, 4) OUTPUT TO_UPPER(out)
Hint:
  • LEFT("BANGLADESH", 4) → "BANG"
  • RIGHT("BANGLADESH", 4) → "DESH"
  • Concatenate with "-" between, then .upper()
  • .upper() has no effect here because the input is already upper-case
Your Turn — LEFT/RIGHT/TO_LOWER translation
Translate the pseudocode to Python and state the output. code <- "Programming" out <- LEFT(code, 4) & "_" & RIGHT(code, 4) OUTPUT TO_LOWER(out)
Hint:
  • Translate each pseudocode call to its Python equivalent on a separate line
  • LEFT("Programming", 4) = "Prog"
  • RIGHT("Programming", 4) = "ming"
Task — MID + LENGTH translation
Translate the pseudocode and state the output. id <- "S0204-A-MOSHIKUR" sid <- LEFT(id, 5) grade <- MID(id, 7, 1) name <- RIGHT(id, LENGTH(id) - 8) OUTPUT sid & "/" & grade & "/" & name
Hint:
  • LEFT(id, 5) → "S0204"
  • MID(id, 7, 1) → 1 character at pseudocode position 7 → Python index 6 → "A"
  • LENGTH(id) = 16, so RIGHT(id, 16-8) = RIGHT(id, 8) → "MOSHIKUR"
Your Turn — MID + LENGTH translation
Translate the pseudocode and state the output. id <- "S1099-B-NAYEEM" sid <- LEFT(id, 5) grade <- MID(id, 7, 1) name <- RIGHT(id, LENGTH(id) - 8) OUTPUT sid & "/" & grade & "/" & name
Hint:
  • Apply the same off-by-one rule
  • LENGTH(id) for "S1099-B-NAYEEM" is 14
  • RIGHT(id, 14-8) = RIGHT(id, 6) = "NAYEEM"
Exam tip:
  • When the question gives a pseudocode algorithm and asks for Python, copy each pseudocode line and replace it with its Python equivalent — do not rewrite the logic from scratch.
  • Stay line-for-line with the pseudocode.
  • The mark scheme is also line-by-line, so this strategy hits each mark point in turn.

7.5 Searching Strings

Validation, file processing, OOP, and exception handling questions all eventually ask "does this string contain X?" or "where does X start in this string?" — usernames, file extensions, command parsers, postcode validators. Paper 4 expects you to write the search loop yourself, not rely on in or .find(). Once you understand the search pattern, every variation (count, replace, split) is built from the same structure.

Searching a string by hand has two shapes. The first is searching for a single character — one loop, one comparison. The second is searching for a substring — an outer loop over starting positions and an inner loop checking the match.

Finding the first position of a character

# Source: moshikur.com | Cambridge A Level CS 9618
text = "banana"
target = "n"
position = -1  # -1 means "not found yet"

for i in range(len(text)):
    if text[i] == target:
        position = i
        break  # stop at the first match

print("First position of", target, ":", position)  # 2
Key rule — first-position search pattern:
  • Set position = -1 as the "not found" sentinel.
  • Loop through indexes 0 to len(s) - 1 using range(len(s)).
  • On a match, record the index and break.
  • After the loop, position is either the first index or still -1.

Finding a substring (manual sliding window)

# Source: moshikur.com | Cambridge A Level CS 9618
text = "CATALOGUE"
sub = "LOG"
n = len(text)
m = len(sub)
found = False
pos = -1

for i in range(n - m + 1):
    match = True
    for j in range(m):
        if text[i + j] != sub[j]:
            match = False
            break
    if match:
        found = True
        pos = i
        break

if found:
    print("Found at position", pos)  # 4
else:
    print("Not found")

Each iteration of the outer loop slides a window of size m one place to the right. The inner loop checks the m characters against the substring. The last valid starting position is n - m, so the outer loop is range(n - m + 1).

Task — startsWithBK(code)
A library system stores book codes as strings. A code is valid only if it starts with "BK-". Write a Python function startsWithBK(code) that returns True when the code begins with the three characters B, K, -, and False otherwise. Do not use the str.startswith method.
Hint:
  • Define the function with one parameter code, returns a Boolean
  • Reject codes that are too short (fewer than 3 characters)
  • Build the first three characters into a string by looping
  • Compare the prefix to "BK-"
Your Turn — endsWithCS(sid)
A school stores student IDs as strings. An ID is valid only if it ends with "-CS" (the last three characters). Write a Python function endsWithCS(sid) that returns True if the ID ends in "-CS", False otherwise. Do not use the str.endswith method.
Hint:
  • Use the parallel of the worked example
  • Take the last three characters with a loop or a slice
  • Compare to "-CS"
Task — firstVowel(s)
Write a Python function firstVowel(s) that returns the index of the first vowel (a, e, i, o, u — either case) in s, or -1 if none is found. Test on "crypt" and "Cambridge".
Hint:
  • Sentinel value: start with position = -1
  • Loop with an index using range(len(s))
  • If the lower-case character is in "aeiou", record the index and break
Your Turn — firstDigit(s)
Write a Python function firstDigit(s) that returns the index of the first digit character (0–9) in s, or -1 if none is found. Test on "hello" and "Room204".
Hint:
  • Use the same shape as firstVowel
  • The condition becomes "0" <= s[i] <= "9"
Task — contains(text, sub)
Write a Python function contains(text, sub) that returns True if sub appears anywhere inside text, False otherwise. Do not use the in operator or .find(). Test on ("PROGRAMMING", "GRAM") and ("PROGRAMMING", "XYZ").
Hint:
  • Set found = False
  • Outer loop slides the window from index 0 to len(text) - len(sub)
  • Inner loop checks the window character by character
  • If all characters match, set found = True and break
Your Turn — findPosition(text, sub)
Write a Python function findPosition(text, sub) that returns the index where sub first appears in text, or -1 if not found. Do not use .find(). Test on ("CATALOGUE", "LOG") and ("CATALOGUE", "DOG").
Hint:
  • Same outer/inner loop as the worked example
  • Instead of a Boolean, return the index i where the match started
  • Initialise pos = -1 as the sentinel
Exam tip:
  • When the question forbids a method, write the long form even if the slice would be one line.
  • Examiners want to see the loop and the comparison because they are the marks.
  • A one-line slice like sid[-3:] == "-CS" may earn only the answer mark, not the algorithm marks.

7.6 Counting & Replacing

Counting is the most common Paper 4 mini-task ("how many vowels", "how many spaces"). Replacing builds directly on it — you walk the same loop but, instead of incrementing a counter, you build a new string. Both are also the foundation for caesar cipher questions, text cleaning, and CSV parsing.

The build-up pattern for replacing characters: start with newText = "", walk the original with a for loop, append either the original character or a substitute, then output newText. Never try to modify the original string in place — strings are immutable.

Counting occurrences of a character

# Source: moshikur.com | Cambridge A Level CS 9618
text = "banana"
target = "a"
count = 0

for ch in text:
    if ch == target:
        count = count + 1

print("Count of", target, ":", count)  # 3

Replacing characters by building a new string

# Source: moshikur.com | Cambridge A Level CS 9618
text = "HELLO"
oldChar = "L"
newChar = "X"
newText = ""

for ch in text:
    if ch == oldChar:
        newText = newText + newChar
    else:
        newText = newText + ch

print(newText)  # HEXXO
Key rule — build-up pattern:
  • Any string transformation (replace, encode, clean) follows the same shape: start with newString = "", walk the original, append either the original or a substitute, then output newString.
  • Never try to modify the original string in place.
Task — countDigits(s)
Write a Python function countDigits(s) that returns the number of digit characters ("0"–"9") in the string s. Then test it on "Room204, Lab3".
Hint:
  • Define the function with one parameter s, returns an integer
  • Initialise the counter: count = 0
  • For each character, test if it is between "0" and "9"
  • Return the count
Your Turn — maskDigits(s)
Write a Python function maskDigits(s) that returns a new string identical to s but with every digit character replaced by an asterisk *. Test it on "PIN: 1234, code: 9X87".
Hint:
  • Same loop as countDigits, but instead of counting, append
  • Initialise newText = ""
  • If the character is between "0" and "9", append "*"
  • Otherwise append the original character
Task — countConsonants(s)
Write a Python function countConsonants(s) that returns the number of consonants in s. A consonant is any letter (a–z or A–Z) that is not a vowel. Test on "Education".
Hint:
  • Counter pattern
  • Lower-case the character once
  • Check it is a letter AND it is not a vowel
Your Turn — countNonLetters(s)
Write a Python function countNonLetters(s) that returns the number of characters in s that are not letters (so digits, spaces, and symbols all count). Test on "S0204-A-MOSHIKUR".
Hint:
  • Same shape as countConsonants
  • Invert the letter check
  • Letter = ("a" <= low <= "z"); non-letter is the opposite
Task — replaceSpaces(s) without .replace()
Write a Python function replaceSpaces(s) that returns a new string with every space replaced by an underscore _. Do not use .replace(). Test on "Computer Science 9618".
Hint:
  • Build-up pattern: newText = ""
  • Loop and append either "_" or the original character
Your Turn — removeVowels(s)
Write a Python function removeVowels(s) that returns a new string with every vowel (a, e, i, o, u — either case) removed. Test on "Bangladesh".
Hint:
  • Same build-up pattern
  • If the character is a vowel, append nothing (skip)
  • Otherwise append it
Exam tip:
  • Comparing characters with >= and <= works in Python because each character has an underlying ASCII value.
  • "0" is 48, "9" is 57, so "0" <= ch <= "9" tests whether ch is a digit.
  • Mark schemes accept either this style or a list of digits inside an if ch in "0123456789" check.

7.7 Reversing & Palindromes

Reversal appears in past Paper 4 questions both directly ("reverse this string") and as a sub-routine inside larger tasks (palindrome checks, number-to-binary output, queue printouts). The palindrome check is a small but rich combination of indexing, looping, and Boolean reasoning — exactly what the mark scheme rewards.

Reversing by reading backwards

# Source: moshikur.com | Cambridge A Level CS 9618
word = "PYTHON"
reversedWord = ""

for i in range(len(word) - 1, -1, -1):
    reversedWord = reversedWord + word[i]

print(reversedWord)  # NOHTYP

The range(len(word) - 1, -1, -1) generates the indexes 5, 4, 3, 2, 1, 0 for "PYTHON". The stop value is -1 because the loop must include index 0 — and range excludes its stop value.

Detecting a palindrome (pair-comparison)

# Source: moshikur.com | Cambridge A Level CS 9618
def isPalindrome(s):
    n = len(s)
    isPal = True
    i = 0
    while i < n // 2 and isPal:
        if s[i] != s[n - 1 - i]:
            isPal = False
        i = i + 1
    return isPal

print(isPalindrome("MADAM"))    # True
print(isPalindrome("PYTHON"))   # False
print(isPalindrome("RACECAR"))  # True
Key rule — pair-comparison palindrome check:
  • Loop only to the middle of the string (n // 2), comparing s[i] with s[n - 1 - i].
  • If any pair differs, the string is not a palindrome.
  • This halves the work compared with reversing and comparing.
Task — reverseString(s) without s[::-1]
Write a Python function reverseString(s) that returns a new string with the characters of s in reverse order. Do not use Python's slice with a negative step (s[::-1]) or the reversed() function. Test it on "ALGORITHM".
Hint:
  • Define the function with one parameter s, returns a string
  • Initialise an empty result string: result = ""
  • Loop backwards using range(len(s) - 1, -1, -1)
  • Append s[i] and return
Your Turn — isPalindrome(s) — case-sensitive
Using the pair-comparison technique, write a Python function isPalindrome(s) that returns True if s reads the same forwards and backwards, False otherwise. Treat the comparison as case-sensitive. Test on "MADAM", "Madam", and "COMPUTER".
Hint:
  • Get n = len(s) and set a flag isPal = True
  • Loop i from 0 while i < n // 2
  • If s[i] differs from s[n - 1 - i], set isPal = False and break
  • Return isPal
Task — reverseWhile(s)
Write a Python function reverseWhile(s) that returns the reverse of s using a while loop instead of for. Do not use slicing or reversed(). Test on "PAPER".
Hint:
  • Initialise result = "" and i = len(s) - 1
  • Loop while i >= 0, appending s[i]
  • Decrement i on each pass
Your Turn — reverseFor(s)
Write a Python function reverseFor(s) that returns the reverse of s using a for loop with range(). Do not use slicing. Test on "COMPUTER".
Hint:
  • range(len(s) - 1, -1, -1) generates the indexes backwards
  • Loop and append s[i] to result
Task — isPalindromeCI(s) — case-insensitive
Write a Python function isPalindromeCI(s) that returns True if s is a palindrome ignoring case. Test on "Madam", "Racecar", and "Hello".
Hint:
  • Convert the whole string to lower case first
  • Apply the pair-comparison palindrome check on the lower-cased version
Your Turn — isPalindromeNS(s) — ignore spaces
Write a Python function isPalindromeNS(s) that returns True if s is a palindrome ignoring spaces (case-sensitive). Test on "taco cat" and "hello world".
Hint:
  • First build a new string with the spaces stripped out
  • Then run the standard pair-comparison check
Exam tip:
  • When the question asks for a palindrome check, do not first reverse the string and then compare.
  • The mark scheme typically lists the pair-comparison approach as the model solution.
  • Following the same approach earns the algorithm marks; the reverse-and-compare version often earns only the final correctness mark.

7.8 random.sample() — picking unique items

This function is on the page because of a real, recent Paper 4 question. In October/November 2025 Paper 42, Question 2(a), candidates were asked to generate 20 unique random integers between 0 and 100. The official Python mark scheme answered with random.sample(range(0, 101), 20). The same function works on strings — and that is what makes it useful here: when you need to pick unique characters from a string (a one-time password, a random initial, a sampled list with no repeats), this is the cleanest answer.

The function random.sample(population, k) returns a list of k unique items chosen from the population. The population can be any sequence — a list, a range, or a string. Because Python strings are sequences of characters, you can pass a string in directly and get back a list of distinct characters.

# Source: moshikur.com | Cambridge A Level CS 9618
import random

# 1. The exact mark-scheme line from 9618/42 O/N 2025 Q2(a)
TheArray = random.sample(range(0, 101), 20)
print(TheArray)
# example: [37, 4, 88, 12, 99, ...] — 20 unique integers, 0–100

# 2. The same idea, applied to a string of characters
letters = "ABCDEFGHIJKLMNOP"
picks = random.sample(letters, 5)
print(picks)
# example: ['F', 'A', 'L', 'B', 'J'] — 5 unique characters

# 3. Join the list back into a string (common follow-up step)
password = "".join(random.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 8))
print(password)
# example: "K7F3LBQ9"
Key rule — sample vs choice:
  • random.sample(seq, k) — returns k unique items (no repeats). Raises ValueError if k > len(seq).
  • random.choice(seq) — returns one item, with repeats possible across calls.
  • random.choices(seq, k=N) — returns N items, repeats allowed.
Use sample when uniqueness matters (lottery picks, unique IDs, password characters with no repeats).
Task — randomPassword(n)
Write a Python function randomPassword(n) that returns an n-character password containing only upper-case letters, with no repeated characters. Use random.sample. If n is greater than 26, return the string "ERROR".
Hint:
  • Import the module: import random at the top
  • Reject impossible lengths: if n > 26, return "ERROR"
  • Pick unique characters: random.sample("ABC...Z", n)
  • Join back into a string with "".join(...)
Your Turn — uniqueDigits(n)
Write a Python function uniqueDigits(n) that returns a string of n unique digit characters from "0123456789", in random order. If n > 10, return the string "INVALID".
Hint:
  • Exactly the same shape as the worked example, but with the digit pool
  • import random
  • Reject n > 10 (only 10 digits available)
  • random.sample("0123456789", n)
  • Join with "".join(...) and return
Task — randomTwo(s)
Write a Python function randomTwo(s) that returns a string of two unique characters picked at random from s. If s has fewer than 2 characters, return the empty string "".
Hint:
  • import random
  • Reject input that is too short (len(s) < 2)
  • random.sample(s, 2) returns a list of 2 distinct characters
  • Join into a string with "".join(...)
Your Turn — randomThree(s)
Write a Python function randomThree(s) that returns a string of three unique characters picked at random from s. If s has fewer than 3 characters, return the string "TOO SHORT".
Hint:
  • Exactly the same shape as the worked example, but with k = 3
  • Different error string: "TOO SHORT"
Task — shuffleString(s)
Write a Python function shuffleString(s) that returns a new string containing the same characters as s in a random order, with no character repeated or omitted. Use random.sample. Test on "ABCDE".
Hint:
  • The key insight: random.sample(s, len(s)) picks every character once, in random order
  • Join the list back into a string
Your Turn — randomDate() — the mark-scheme pattern
Write a Python function randomDate() that uses random.sample(range(1, 31), 5) to return a list of 5 unique day numbers between 1 and 30. Print the result.
Hint:
  • This is the direct mark-scheme pattern from O/N 2025 Q2(a)
  • Watch the exclusive upper bound: range(1, 31) gives 1 to 30 inclusive
Exam tip:
  • Cambridge accepts random.sample(range(0, 101), 20) as the model Python answer for "20 unique random integers between 0 and 100 (inclusive)" — see 9618/42 O/N 2025 mark scheme.
  • Remember the upper bound is exclusive in range(), so you write range(0, 101), not range(0, 100).
  • Test the boundary case in your head before you write the call.

7.9 Exam-Style Task

Individual techniques are useful, but Paper 4 marks reward you for assembling them into one complete, working program. This task combines indexing, searching, counting, and output formatting. Treat it like a real exam — write your code first, then check against the model solution.

Question (Paper 4 style) — 12 marks

A school stores a single line of registration data for each student as a string of the format:

SXXXX-G-NAME

where:

  • SXXXX is a 5-character student ID starting with the letter S followed by 4 digits
  • G is a single grade character ('A', 'B' or 'C')
  • NAME is the student's name (at least 1 character)
  • The two hyphens - always appear in fixed positions

Examples of valid records: S0204-A-MOSHIKUR, S1099-B-NAYEEM.

Write a Python program that:

  • Asks the user to enter a registration record.
  • Validates that the record begins with "S" followed by 4 digits, that the 7th character is one of "A", "B", "C", and that hyphens appear at Python indexes 5 and 7. [6]
  • If the record is valid, outputs the student ID, grade, and name on separate lines. [3]
  • If the record is invalid, outputs a clear error message identifying which part failed. [3]

Total marks: 12 (AO3)

Lab Task — SXXXX-G-NAME registration validator [12 marks]
Write the full Python program for the registration validator described above. Plan the structure first, validate the three things in sequence, then split the record into three substrings if everything passes.
Hint:
  • Read the input with input()
  • Validate the ID: first character must be "S"; characters at indexes 1 to 4 must be digits
  • Validate the structure: index 5 must be "-"; index 6 must be a grade letter; index 7 must be "-"
  • The name (from index 8 onwards) must contain at least one character
  • If valid, extract studentID = record[0:5], grade = record[6], name = record[8:]
  • Output the three parts on separate lines with labels
  • For invalid records, print a clear error message

Mark scheme (model)

Marking pointAOMarks
Input read using input() and stored in a variableAO31
Check that first character is "S"AO31
Loop or four explicit checks for digits at indexes 1–4AO32
Check hyphens at indexes 5 and 7AO31
Check grade character is one of A, B, CAO31
Extract student ID, grade, name using slicing or loopsAO33
Output on three separate lines with labelsAO31
Sensible error message for each invalid caseAO32

Total: 12. Accept equivalent algorithms — e.g. building a digit-check using "0123456789" membership instead of comparison, or using .startswith if not explicitly forbidden.

Exam tip:
  • In the real exam you will not have a live playground — but you will have scrap paper.
  • Before writing your code, draw the index row underneath the string in the question (positive numbers above, negative below).
  • Two minutes of drawing saves five minutes of debugging.

Key Points Summary

A string is an ordered, immutable sequence of characters — written between single or double quotes.
Strings are immutable: word[0] = "B" raises TypeError. To change a string, build a new one and reassign.
Python strings are 0-indexed; negative indexes count from the end (word[-1] is the last character).
Slice s[start:end] includes start, excludes end. Slice length is always end - start.
MID(s, x, y) in pseudocode → s[x-1 : x-1+y] in Python. The most-tested translation in Paper 4.
LEFT(s, n) → s[:n]; RIGHT(s, n) → s[-n:]; TO_UPPER → s.upper(); TO_LOWER → s.lower(); & → +.
Counter pattern: count = 0; for ch in s: count = count + 1 — full marks for the algorithm, not len().
Build-up pattern: newText = ""; for ch in s: append either substitute or original. Never modify in place.
First-position search: position = -1 (sentinel); loop with range(len(s)); break on first match.
Substring search: outer loop slides window (range(n - m + 1)); inner loop checks each character.
Reverse with range(len(s) - 1, -1, -1) — stop value -1 because range excludes the stop.
Palindrome check: loop only to n // 2, comparing s[i] with s[n - 1 - i]. Halves the work.
random.sample(population, k) returns k unique items. random.choices allows repeats. random.choice returns one.
random.sample(range(0, 101), 20) — the exact mark-scheme line for 20 unique ints 0–100 inclusive (O/N 2025 Q2a).
When the question forbids a method, write the long form. Examiners reward the loop and comparison, not the one-liner.

7.10 Practice Tasks

Fifteen exam-style string tasks. Each shows only the question — click Hint for the thought process, or Help for the worked Python solution. Try each one yourself before revealing.

Exam tip:
  • For every string task, the marker checks: (1) correct use of slicing with the off-by-one rule for MID, (2) the build-up pattern (never item assignment), (3) the counter pattern when counting, (4) the pair-comparison palindrome check, (5) random.sample for unique picks.
  • The most common mark loss is using len(), .replace(), or s[::-1] when the question forbids them.
1Practice Task — Predict output — strings and immutability [2 marks]
State the output of this code and explain what error appears if you uncomment the last line. word = "Bat" print(word) # word[2] = "e"
2Practice Task — Concatenation: + vs print comma [2 marks]
State the output of this program. a = "Room" b = "5" print(a + b) print(a, b)
3Practice Task — Indexing — positive and negative [3 marks]
Given word = "BANANA", state the value of each expression. • word[0] • word[-1] • word[3] • word[-2]
4Practice Task — Translate MID to Python slicing [3 marks]
Write a single Python expression equivalent to pseudocode MID("EXAMINATION", 3, 5) and state the result.
5Practice Task — Slicing — first half and second half [3 marks]
Given s = "PROGRAMMING" (length 11), write Python expressions for: • the first 5 characters • the last 6 characters • characters from index 3 to 7 (inclusive)
6Practice Task — countChars(s) without len() [3 marks]
Write a Python function countChars(s) that returns the number of characters in s without using len(). Test on "Hello!" and the empty string.
7Practice Task — Translate full pseudocode to Python [4 marks]
Translate this pseudocode into Python and state the output. w <- "ARCHITECTURE" p1 <- LEFT(w, 4) p2 <- MID(w, 5, 4) p3 <- RIGHT(w, 3) result <- p1 & "-" & p2 & "-" & TO_UPPER(p3) OUTPUT result
8Practice Task — countVowels(s) [3 marks]
Write a Python function countVowels(s) that returns the number of vowels (a, e, i, o, u — either case) in s. Do not use .count(). Test on "Cambridge".
9Practice Task — startsWithPrefix(s, prefix) — manual [4 marks]
Write a Python function startsWithPrefix(s, prefix) that returns True if s starts with the given prefix, False otherwise. Do not use .startswith(). Hint: build the first len(prefix) characters of s by looping and compare.
10Practice Task — maskDigits(s) [4 marks]
Write a Python function maskDigits(s) that returns a new string with every digit character replaced by "*". Test on "Phone: 01711-12345".
11Practice Task — reverseString(s) without s[::-1] [4 marks]
Write a Python function reverseString(s) that returns a new string with the characters of s in reverse order. Do not use slicing with a negative step or reversed(). Test on "COMPUTER".
12Practice Task — isPalindrome(s) — pair comparison [4 marks]
Using the pair-comparison technique, write a Python function isPalindrome(s) that returns True if s is a palindrome (case-sensitive). Test on "RADAR", "HELLO", "Kayak".
13Practice Task — randomPassword(n) — unique letters [4 marks]
Write a Python function randomPassword(n) that returns an n-character password of unique upper-case letters using random.sample. If n > 26, return "ERROR". Test on n=6 and n=27.
14Practice Task — Mark-scheme line — 20 unique random integers [3 marks]
Write the exact Python mark-scheme line (from 9618/42 O/N 2025 Q2a) that generates 20 unique random integers between 0 and 100 inclusive. Print the result.
15Practice Task — Full validator — postal code [5 marks]
A postal code has the format DDLL-DDDD (2 digits, 2 letters, hyphen, 4 digits). Write a Python function isValidPostcode(code) that returns True if the code matches this format, False otherwise. Do not use .isdigit() or .isalpha(). Test on "12AB-3456", "1A2B-3456", "12AB-345".

Question Bank

Answer all questions, then press Submit Quiz to see your score.

0/12 answered

Question 1Multiple Choice

What happens when you try word[0] = "B" in Python where word = "Cat"?

Question 2True / False

In Python, the expression "Lab" + "204" produces "Lab 204" (with a space).

Question 3Multiple Choice

Given word = "PYTHON", what does word[-3] return?

Question 4Multiple Choice

Which Python slice is equivalent to pseudocode MID("ALGORITHM", 4, 5)?

Question 5True / False

The slice s[2:5] always returns exactly 3 characters (regardless of what s is, as long as s is long enough).

Question 6Multiple Choice

Without using len(), which function correctly counts vowels in a string?

Question 7Multiple Choice

What is the Python equivalent of pseudocode RIGHT(s, 3)?

Question 8True / False

When searching for the first position of a character, you should initialise position = -1 as the "not found" sentinel.

Question 9Multiple Choice

What is the output of maskDigits("PIN: 9X87")?

Question 10True / False

isPalindrome("RACECAR") returns True using the pair-comparison check.

Question 11Multiple Choice

Which call generates 20 unique random integers from 0 to 100 inclusive (per the O/N 2025 mark scheme)?

Question 12True / False

In the build-up replacement pattern, you should always modify the original string in place to save memory.

Answer all 12 questions to enable submission.