Each replay shows a student solving a Python problem in real time. Step through code frame by frame — see what they were thinking, where they went wrong, and what we can learn from their journey.
# and print each result. Vowels (a, e, i, o, u — upper and lower) stay unchanged.Input: 3
hello
Python
World
Output: #e##o
#y##o#
#o###
This student eventually got full marks — but took over 80 attempts to get there. Watch how three completely different bugs appear right from the start, and how the student layers on complexity until finally landing on a clean, correct solution.
n=int(input())i=0lst=[]whille(i<n): s=str(input()) lst.append(s) i+=1for i in lst: for ch in i: if ch=='a' or ch=='e' or ch=='i' or ch=='o' or ch=='u' or ch=='A' or ch=='E' or ch=='I' or ch=='O' or CH=='U': continue else: s[ch]='#'Three Bugs, One ShotLine 4: whille is a typo — Python crashes before the code even starts. Line 10: CH is a new variable name, but ch (lowercase) is what the loop gives us — Python treats these as different names. Line 13: You cannot change a single letter in a Python string by position — strings are locked once created.💭 Mental model: "I can modify a string the same way I'd write to an array slot." This is the most common beginner Python misconception.for k in lst: print(k) print()# n=int(input())# i=0# lst=[]# while(i<n):# s=str(input())# lst.append(list(s))# i+=1# for words in range(0,len(lst)):# for ch in range(0,len(lst[words])):# if lst[words[ch]]=='a' or lst[words[ch]]=='e' or lst[words[ch]]=='i' or lst[words[ch]]=='o' or lst[words[ch]]=='u' or lst[words[ch]]=='A' or lst[words[ch]]=='E' or lst[words[ch]]=='I' or lst[words[ch]]=='O' or lst[words[ch]]=='U':# continue# else:# lst[words[ch]]='#'# for k in lst:# print(str(k))# print() n=int(input())l=0lst=[]while(l<n): s=str(input()) s=list(s) for i in s: if i=='a' or i=='e' or i=='i' or i=='o' or i=='o' or i=='A' or i=='E' or i=='I' or i=='O' or i=='U': continue else: i='#'Runtime → Wrong Answer: Progress!The typo is fixed — now the code at least runs. But it still gives the wrong output. Line 29: i = '#' — the student is updating the loop variable i, but that doesn't change the original string. i is a temporary copy for each step through the loop. The string itself is untouched.💭 Mental model: "If I change the loop variable, the original data changes too." This is a misunderstanding of how Python's for-loop works. lst.append(str(s)) l+=1for j in lst: print(j) print()# n=int(input())# i=0# lst=[]# while(i<n):# s=str(input())# lst.append(list(s))# i+=1# for words in range(0,len(lst)):# for ch in range(0,len(lst[words])):# if lst[words[ch]]=='a' or lst[words[ch]]=='e' or lst[words[ch]]=='i' or lst[words[ch]]=='o' or lst[words[ch]]=='u' or lst[words[ch]]=='A' or lst[words[ch]]=='E' or lst[words[ch]]=='I' or lst[words[ch]]=='O' or lst[words[ch]]=='U':# continue# else:# lst[words[ch]]='#'# for k in lst:# print(str(k))# print() n=int(input())l=0ss=''lst=[]while(l<n): s=list(input()) s=list(s) for i in range(0,len(s)): if s[i]=='a' or s[i]=='e' or s[i]=='i' or s[i]=='o' or s[i]=='o' or s[i]=='A' or s[i]=='E' or s[i]=='I' or s[i]=='O' or s[i]=='U': # continue ss+=s[i] elif s[i]==' ': # continue ss+=s[i] else: # s[i]="#" ss[i]='#'Adding Complexity Without ProgressThe code has grown from 15 lines to 42. There are now print statements inside loops, commented-out old attempts, and a new variable ss — but Line 35: ss[i] = '#' hits the exact same wall as before: you still can't overwrite a character in a string by index.💭 Mental model: "My approach is right, I just need to do it on a different variable." The real issue (strings can't be changed in place) hasn't been understood yet. # s=str(s) lst.append(s) l+=1# for j in lst: # print(str(j)) # print()print(ss)# n=int(input())# i=0# lst=[]# while(i<n):# s=str(input())# lst.append(list(s))# i+=1# for words in range(0,len(lst)):# for ch in range(0,len(lst[words])):# if lst[words[ch]]=='a' or lst[words[ch]]=='e' or lst[words[ch]]=='i' or lst[words[ch]]=='o' or lst[words[ch]]=='u' or lst[words[ch]]=='A' or lst[words[ch]]=='E' or lst[words[ch]]=='I' or lst[words[ch]]=='O' or lst[words[ch]]=='U':# continue# else:# lst[words[ch]]='#'# for k in lst:# print(str(k))# print() n=int(input())l=0ss=''lst=[]while(l<n): s=list(input()) s=list(s) for i in range(0,len(s)): if s[i]=='a' or s[i]=='e' or s[i]=='i' or s[i]=='o' or s[i]=='o' or s[i]=='A' or s[i]=='E' or s[i]=='I' or s[i]=='O' or s[i]=='U': # ss.append(s[i]) # print(s[i]) continue elif s[i]==' ': # ss.append(s[i]) continue # print(s[i]) else: # ss.append('#') s[i]="#" # print('#')One Test Passes — The Insight Arrives1 out of 3 test cases passes. The key change: Lines 36–38 — instead of trying to modify the string directly, the student now converts it to a list first. Lists can be changed by position. This is the breakthrough — the algorithm is now fundamentally correct.💭 Mental model shifting to: "Convert to list, change the item, convert back." This is the correct approach. # s=str(s) lst.append(s) # print() l+=1# for j in range(0,len(lst)):# ss+=str(lst[j]) # print(str(j)) # print() # print(ss)# print(lst)for m in lst: for p in range(0,len(m)): ss+=m[p]print(ss)# n=int(input())# i=0# lst=[]# while(i<n):# s=str(input())# lst.append(list(s))# i+=1# for words in range(0,len(lst)):# for ch in range(0,len(lst[words])):# if lst[words[ch]]=='a' or lst[words[ch]]=='e' or lst[words[ch]]=='i' or lst[words[ch]]=='o' or lst[words[ch]]=='u' or lst[words[ch]]=='A' or lst[words[ch]]=='E' or lst[words[ch]]=='I' or lst[words[ch]]=='O' or lst[words[ch]]=='U':# continue# else:# lst[words[ch]]='#'# for k in lst:# print(str(k))# print() n=int(input())l=0ss=''lst=[]while(l<n): s=list(input()) s=list(s) for i in range(0,len(s)): if s[i]=='a' or s[i]=='e' or s[i]=='i' or s[i]=='o' or s[i]=='u' or s[i]=='A' or s[i]=='E' or s[i]=='I' or s[i]=='O' or s[i]=='U': # ss.append(s[i]) # print(s[i]) continue elif s[i]==' ': # ss.append(s[i]) continue # print(s[i]) else: # ss.append('#') s[i]="#" # print('#') # s=str(s) lst.append(s) # print() l+=1# for j in range(0,len(lst)):# ss+=str(lst[j]) # print(str(j)) # print() # print(ss)# print(lst)for m in lst: ss='' for p in range(0,len(m)): ss+=m[p]All Public Tests Pass 🟢All 3 public tests pass. Lines 49–52 contain the actual solution — everything above is commented-out old attempts the student kept "just in case." The logic: convert to list → replace non-vowels → join back and print.💭 Common behaviour: students keep old code around even after it's superseded, in case they need to "go back." print(ss)n=int(input())l=0ss=''lst=[]while(l<n): s=list(input()) s=list(s) for i in range(0,len(s)): if s[i]=='a' or s[i]=='e' or s[i]=='i' or s[i]=='o' or s[i]=='u' or s[i]=='A' or s[i]=='E' or s[i]=='I' or s[i]=='O' or s[i]=='U': # ss.append(s[i]) # print(s[i]) continue elif s[i]==' ':Private Tests Pass Too ✓The student cleaned up the code and submitted to the harder private tests. All passed. Lines 8–13 show the final, lean version — the logic generalised correctly, not just matched the visible examples.💭 This is what real learning looks like: the student arrived at a solution they understand, not just one they copied. # ss.append(s[i]) continue # print(s[i]) else: # ss.append('#') s[i]="#" # print('#') # s=str(s) lst.append(s) # print() l+=1# for j in range(0,len(lst)):# ss+=str(lst[j]) # print(str(j)) # print() # print(ss)# print(lst)for m in lst: ss='' for p in range(0,len(m)): ss+=m[p] print(ss)s = "hello"; s[0] = "H". Then show the fix: s = list(s); s[0] = "H"; s = "".join(s). A 30-second live demo is worth ten explanations.9876 → True (9 > 8 > 7 > 6) 4321 → True 4312 → False (3 > 1, but 1 < 2 at the end) 1234 → False
This 44-second session is a masterclass in focused debugging. The student makes small, deliberate changes and tests after each one. Compare this to Replay 5 where 213 attempts achieve nothing — same problem difficulty, opposite behaviour.
def is_decreasing_number(n: int) -> bool: ''' Given a 4-digit number, check if its digits are strictly decreasing from left to right. Examples: >>> is_decreasing_number(4321) True >>> is_decreasing_number(4312) False >>> is_decreasing_number(9876) True >>> is_decreasing_number(1111) False >>> is_decreasing_number(3210) True Args: n (int): A 4-digit integer Returns: bool: True if the number is decreasing, False otherwise ''' string1= str(n) list1 = list(string) for k in list1: if list1[k]>list1[k+1]:Two Bugs, Adjacent LinesThe algorithm is on the right track — convert to string, compare digits. But Line 26: the variable is string1, but the code writes list(string) — wrong name, causing a crash. And Line 28: k comes from the for-loop and is a character, not an index, so list1[k] would fail even if line 26 were fixed.💭 Mental model: "I know what I want to do, I just need to figure out the right variable names." The student is close — understanding is there, execution is off. return True else: return Falsedef is_decreasing_number(n: int) -> bool: ''' Given a 4-digit number, check if its digits are strictly decreasing from left to right. Examples: >>> is_decreasing_number(4321) True >>> is_decreasing_number(4312) False >>> is_decreasing_number(9876) True >>> is_decreasing_number(1111) False >>> is_decreasing_number(3210) True Args: n (int): A 4-digit integer Returns: bool: True if the number is decreasing, False otherwise ''' string1= str(n) list1 = list(n)Still Stuck: Can't List an IntegerVariable name is fixed, but now Line 26: list(n) — you can't turn an integer directly into a list of its digits. The path to digits is: integer → string → list of character digits. The student is one step away.💭 Mental model: "I should be able to break the number into parts directly." They're not yet thinking of the integer→string→list conversion chain. for k in list1: if list1[k]>list1[k+1]: return True else: return Falsedef is_decreasing_number(n: int) -> bool: ''' Given a 4-digit number, check if its digits are strictly decreasing from left to right. Examples: >>> is_decreasing_number(4321) True >>> is_decreasing_number(4312) False >>> is_decreasing_number(9876) True >>> is_decreasing_number(1111) False >>> is_decreasing_number(3210) True Args: n (int): A 4-digit integer Returns: bool: True if the number is decreasing, False otherwise ''' string1 = str(n) list1 = list(string1) for k in list1: if list1[0]>list1[1]:All Tests Pass — But Is It Right?All 3 public tests pass. But Line 28 only compares the first two digits: list1[0] > list1[1]. A truly "decreasing" number needs all four consecutive pairs checked. The student doesn't know this yet — the public tests happened to pass anyway.💭 This is the "lucky pass" — code that works on the visible examples but is incomplete. A teachable moment: passing tests doesn't prove correctness. return True else: return Falsedef is_decreasing_number(n: int) -> bool: ''' Given a 4-digit number, check if its digits are strictly decreasing from left to right. Examples: >>> is_decreasing_number(4321) True >>> is_decreasing_number(4312) False >>> is_decreasing_number(9876) True >>> is_decreasing_number(1111) False >>> is_decreasing_number(3210) True Args: n (int): A 4-digit integer Returns: bool: True if the number is decreasing, False otherwise ''' string1 = str(n) list1 = list(string1) for k in list1: if list1[2]>list1[1]:Regression: Fixing What Wasn't BrokenThe student tries to improve the comparison and accidentally swaps the indices — Line 28: now reads list1[2] > list1[1] (checking the wrong pair). This is a classic trap: the impulse to "clean up" code that's working, without re-running tests after each change.💭 Mental model: "More changes = more correct." The safer habit: make one change, test, then continue. return False else: return Truedef is_decreasing_number(n: int) -> bool: ''' Given a 4-digit number, check if its digits are strictly decreasing from left to right. Examples: >>> is_decreasing_number(4321) True >>> is_decreasing_number(4312) False >>> is_decreasing_number(9876) True >>> is_decreasing_number(1111) False >>> is_decreasing_number(3210) True Args: n (int): A 4-digit integer Returns: bool: True if the number is decreasing, False otherwise ''' string1 = str(n) list1 = list(string1) for k in list1: if list1[0]>list1[1] and list1[1]> list1[2] and list1[2]> list1[3]:Back to Passing — Final Submission ✓All tests pass again. The code still only checks one pair of adjacent digits — and gets away with it because the test set doesn't expose the gap. A rich classroom discussion: can you write a test case that would break this?💭 The student's mental model is now "check if first digit > second digit = decreasing number." This works for the tests given, but isn't the full rule. return True else: return False"The quick brown fox jumps over the lazy dog" → True "Hello world" → False "Pack my box with five dozen liquor jugs" → True
This replay shows the "false summit" trap. At 47 seconds the student passes all visible tests — and feels done. But hidden tests fail immediately, and now they must figure out why without being able to see those hidden tests. This skill — reasoning about invisible cases — is rarely taught directly.
setdef is_pangram(text: str) -> bool: ''' Given a string, check if it is a pangram (contains all letters of the alphabet at least once). Examples: >>> is_pangram("the quick brown fox jumps over the lazy dog") True >>> is_pangram("this is not a pangram") False >>> is_pangram("abcdefghijklmnopqrstuvwxyz") True >>> is_pangram("zyxwvutsrqponmlkjihgfedcba") True Args: text (str): The input string Returns: bool: True if the string is a pangram, False otherwise ''' alphabets=string.ascii_lowercase text1=text.lower() for i in text1: if i not in alphabets: return FalseLogic Backwards From the StartThe question is "does the text contain all 26 letters?" But the code asks "is each character a letter?" — the opposite direction. Also, Line 25: the return True is inside the loop, so the function stops and returns after checking just the first character.💭 Mental model: "I need to check the text against the alphabet" — but implementing it as "for each letter in text, check if it's in the alphabet" rather than "for each letter in the alphabet, check if it's in the text." return True def is_pangram(text: str) -> bool: ''' Given a string, check if it is a pangram (contains all letters of the alphabet at least once). Examples: >>> is_pangram("the quick brown fox jumps over the lazy dog") True >>> is_pangram("this is not a pangram") False >>> is_pangram("abcdefghijklmnopqrstuvwxyz") True >>> is_pangram("zyxwvutsrqponmlkjihgfedcba") True Args: text (str): The input string Returns: bool: True if the string is a pangram, False otherwise ''' alphabets=string.ascii_lowercase text1=text.lower() for i in text1: if i not in alphabets and int(i)=False:Patching a Broken ApproachThe student tries to exclude non-letter characters: Line 24: int(i)=False — this isn't valid Python at all. You can't assign to a function call. They're trying to filter out numbers and punctuation, but the approach (checking each character instead of checking all 26 letters) is still fundamentally off.💭 Mental model: "My loop structure is correct, I just need to exclude the wrong kinds of characters." The loop structure itself is the problem. return False return True def is_pangram(text: str) -> bool: ''' Given a string, check if it is a pangram (contains all letters of the alphabet at least once). Examples: >>> is_pangram("the quick brown fox jumps over the lazy dog") True >>> is_pangram("this is not a pangram") False >>> is_pangram("abcdefghijklmnopqrstuvwxyz") True >>> is_pangram("zyxwvutsrqponmlkjihgfedcba") True Args: text (str): The input string Returns:Better Idea — With a Hidden BugA fresh approach: strip spaces, count how many characters are in the alphabet. If the count reaches 26, it's a pangram. This is much closer! But Lines 12–17 count every character that appears in the alphabet, including repeats. A string like "aaa...a" (repeated 26 times) would wrongly count as a pangram.💭 Mental model: "If I've seen 26 alphabet letters in the text, I've seen all of them." True if each letter counted once — false if repeats inflate the count. bool: True if the string is a pangram, False otherwise ''' alphabets=string.ascii_lowercase numbers='1234567890' text1=text.lower() for i in text1: if i not in alphabets and i not in numbers: return False return True def is_pangram(text: str) -> bool: alphabets=string.ascii_lowercase text1=text.lower().replace(' ','') '''for i in range(len(text1)): if text[i] not in alphabets: return False return True''' count=0 for i in range(len(text1)): if text1[i] in alphabets: count+=1 False Summit: All Public Tests Pass 🟢All 3 public tests pass at 47 seconds. The counting logic works for the visible test cases — they don't include repeated-letter inputs. But the hidden tests do, and they will expose the bug. The student doesn't know this moment of confidence is about to break.💭 This is the "false summit" — visible success masking hidden failure. A crucial concept: passing tests proves the code works for those inputs, not for all inputs. if count>=26: return True return False def is_pangram(text: str) -> bool: alphabets=string.ascii_lowercase text1=text.lower().replace(' ','') count=0 for i in range(len(text1)): if text1[i] in alphabets: count+=1 if count>=26: return True return FalseHidden Tests Reveal the Bug: 1/3Private tests: only 1/3 pass. The hidden cases almost certainly include inputs with repeated letters. Lines 6–12: the student is now reasoning — "why does 3/3 public become 1/3 private?" They need to think of an input that would fool their count logic. This is adversarial thinking — rare and valuable.💭 The gap between 3/3 public and 1/3 private is the signal. The question is whether the student can reason about it without seeing the hidden tests. def is_pangram(text: str) -> bool: alphabets=string.ascii_lowercase text1=text.lower().replace(' ','') uniq=set() for i in range(len(text1)): if text1[i] in alphabets: uniq.add(text1[i]) length=len(uniq) if length>=26: return TrueThe Fix: Unique Letters via Set ✓The solution: use a set to collect unique letters. Sets automatically discard duplicates. If the set contains all 26 letters, it's a pangram. Lines 6–15 — cleaner, more correct, and more Pythonic than the counting approach.💭 The insight: "I don't want to count letters, I want to collect unique letters." This is what a set is made for. return Falselen(set("the quick brown fox...")) == 26 — one line, correct, idiomatic Python.True if the input string starts with "Hello " or "Hi " (with a space after each). Return False otherwise."Hello there" → True "Hi there" → True "HiThere" → False (no space) "hello there" → False (lowercase) "Hey there" → False
The correct solution is two lines. This student took 168 attempts over 110 seconds, building complex code before arriving at the simple answer. A case study in why beginners over-engineer: they don't know what built-in tools exist, so they implement everything manually.
|| — the "or" from other programming languages, not Pythondef starts_with_greeting(s): """ Checks whether a given string starts with 'Hello ' or 'Hi '. Args: s (str): The string to check. Returns: bool: True if the string starts with 'Hello ' or 'Hi ', False otherwise. Examples: >>> starts_with_greeting('Hello there') True >>> starts_with_greeting('Hi friend') True >>> starts_with_greeting('Good morning') False >>> starts_with_greeting('HiThere') False """ ... if s.startswith('Hello'|| 'Hi'):Wrong Language: || Is Not PythonLine 22: s.startswith('Hello'|| 'Hi') — || is the "or" operator in JavaScript and C, not Python. Python uses the word or. This causes a crash before anything runs. A common error when students switch between languages or guess syntax.💭 Mental model: "or" works like in other languages I've used. Students who've coded in JavaScript or C++ bring their syntax with them. return True return False def starts_with_greeting(s): """ Checks whether a given string starts with 'Hello ' or 'Hi '. Args: s (str): The string to check. Returns: bool: True if the string starts with 'Hello ' or 'Hi ', False otherwise. Examples: >>> starts_with_greeting('Hello there') True >>> starts_with_greeting('Hi friend') True >>> starts_with_greeting('Good morning') False >>> starts_with_greeting('HiThere') False """ ... if s.startswith('Hello'): if s[5]="\t": return True else:Manual Character Checking27 seconds and 42 attempts in. The student has split the logic into separate blocks for "Hello" and "Hi". But Line 23: if s[5]="\t" has two bugs: = should be ==, and \t is a tab character, not a space. They're trying to check whether there's a space after the greeting — but using the wrong character.💭 Mental model: "I'll check each character by position." Instead of using a built-in like startswith(), they're reimplementing what it already does. return False if s.startswith('Hi'): if s[2]="\t": return True else: return False return False def starts_with_greeting(s): """ Checks whether a given string starts with 'Hello ' or 'Hi '. Args: s (str): The string to check. Returns: bool: True if the string starts with 'Hello ' or 'Hi ', False otherwise. Examples: >>> starts_with_greeting('Hello there') True >>> starts_with_greeting('Hi friend') True >>> starts_with_greeting('Good morning') False >>> starts_with_greeting('HiThere') False """ ... if s=='Hithere': return False if s.startswith('Hello'or 'Hi' or'hello' or 'hi'):All Tests Pass — But Look at the CodeAll 4 public tests pass. But the code works by accident. Line 22: if s=='Hithere' — a hard-coded special case for one specific string. Line 24: startswith('Hello' or 'Hi') — in Python, 'Hello' or 'Hi' evaluates to just 'Hello' (Python returns the first "true" value). So this only checks for Hello, not Hi.💭 Mental model: "or inside the function means check both." But Python evaluates 'Hello' or 'Hi' as an expression before passing it to the function — and it simplifies to 'Hello'. return True elif s.startswith('Hi'): return True return False def starts_with_greeting(s): """ Checks whether a given string starts with 'Hello ' or 'Hi '. Args: s (str): The string to check. Returns: bool: True if the string starts with 'Hello ' or 'Hi ', False otherwise. Examples: >>> starts_with_greeting('Hello there') True >>> starts_with_greeting('Hi friend') True >>> starts_with_greeting('Good morning') False >>> starts_with_greeting('HiThere') False """ ... if s.startswith('Hello'): return True if s.startswith('Hi'): if len(s[2])==0:Private Tests Fail — Complexity GrowsAfter the public pass, private tests reveal only 2/3. The student adds more conditions, but now public tests also slip. Line 27: if len(s[2])==0 — s[2] is always a single character (length 1, never 0). This condition never triggers. The code is getting longer but not more correct.💭 Mental model: "Adding more conditions will cover more cases." But the core logic is still wrong, so adding conditions just creates new failure modes. return True else: return False return False def starts_with_greeting(s): """ Checks whether a given string starts with 'Hello ' or 'Hi '. Args: s (str): The string to check. Returns: bool: True if the string starts with 'Hello ' or 'Hi ', False otherwise. Examples: >>> starts_with_greeting('Hello there') True >>> starts_with_greeting('Hi friend') True >>> starts_with_greeting('Good morning') False >>> starts_with_greeting('HiThere') False """ ... t=[] t.append(s.split(" ")) if s.startswith('Hello ') and len(t[0])<=5: return True if s.startswith('Hi ') and len(t[0])==2: return TrueOver-Engineering PeakLines 22–31: the student is now splitting the string, counting words, using lists. This is far more complex than needed. The code works for public tests but fails private ones. Notice how far the code has drifted from the original simple idea.💭 The student has lost sight of the problem. When code grows this complex for a simple rule, it's usually a sign the core approach needs to change, not expand. return False def starts_with_greeting(s): """ Checks whether a given string starts with 'Hello ' or 'Hi '. Args: s (str): The string to check. Returns: bool: True if the string starts with 'Hello ' or 'Hi ', False otherwise. Examples: >>> starts_with_greeting('Hello there') True >>> starts_with_greeting('Hi friend') True >>> starts_with_greeting('Good morning') False >>> starts_with_greeting('HiThere') False """ ... if s.startswith('Hello '): return TrueThe Simple Solution ✓After 159 attempts: two startswith() calls. Lines 23–24. That's it. The entire 107-second journey ends at the two-line solution that was available from the start.💭 This is what happens when a student discovers the right built-in at the end: everything they built manually becomes unnecessary. The lesson: look for built-in functions before implementing manually. if s.startswith('Hi '): return True return False startswith() implement it manually — and get it wrong. Teaching built-in string methods directly prevents this whole class of errors.|| for "or" is valid in several other languages. When students switch to Python, they bring their syntax habits. Error messages that say "invalid syntax" don't explain why — students need to be told explicitly.'Hello' or 'Hi' evaluates to 'Hello' — the first truthy value. This surprises beginners who expect it to mean "either of these." Worth teaching as a distinct concept.if s == 'Hithere', they're matching test output, not solving the problem. This pattern deserves a gentle but direct conversation."Hello there".startswith("Hello ") in a Python shell. Ask: "What does this return? What about "Hi there".startswith("Hi ")?" Then: "How would you combine these two checks?" The student often solves it in under a minute once they know the tool exists.[1, 2, 3] → [9, 4, 1] (3²=9, 2²=4, 1²=1) [2, 4] → [16, 4]
The complete solution is one short line. This student made 213 attempts over 147 seconds and never reached a passing score. This is the "thrashing" pattern — lots of activity, no progress. Watching this session reveals a specific knowledge gap: the student never understood how to build a new list from an old one.
squares() — a function that does not exist in Pythondef reversed_squares(l): """ Takes a list of numbers and returns a new list containing the squares of the elements in reverse order. Args: l (list): A list of numbers. Returns: list: A new list with squares in reverse order. Examples: >>> reversed_squares([1, 2, 3]) [9, 4, 1] >>> reversed_squares([]) [] >>> reversed_squares([-2, 5]) [25, 4] """ ... return squares(l[::-2])Imagining a Function That Doesn't ExistLine 21: return squares(l[::-2]). Python has no built-in squares() function. The student is guessing at the API — inventing a function name that sounds right. Also, l[::-2] steps backwards by 2 (skipping every other element), not a full reversal. Both ideas are wrong from the start.💭 Mental model: "There must be a built-in that does this." When students don't know the right tools, they invent plausible-sounding names. This is a vocabulary gap, not a logic gap.def reversed_squares(l): """ Takes a list of numbers and returns a new list containing the squares of the elements in reverse order. Args: l (list): A list of numbers. Returns: list: A new list with squares in reverse order. Examples: >>> reversed_squares([1, 2, 3]) [9, 4, 1] >>> reversed_squares([]) [] >>> reversed_squares([-2, 5]) [25, 4] """ ... for i in l: l=l[i]**l[i]Overwriting the List Instead of Building a New One36 seconds in. Now using a loop — but Line 23: l = l[i] ** l[i] replaces the entire list l with a single number (the element squared by itself, not even x²). After the first iteration, l is no longer a list at all. The loop then crashes trying to access a number like a list.💭 Mental model: "I can update the list as I go." Students who haven't yet learned to build a new, separate result list will try to modify the original — which destroys it during iteration. return l[::-1] for i in l: l=l[i]**2 return l[::-1]def reversed_squares(l): """ Takes a list of numbers and returns a new list containing the squares of the elements in reverse order. Args: l (list): A list of numbers. Returns: list: A new list with squares in reverse order. Examples: >>> reversed_squares([1, 2, 3]) [9, 4, 1] >>> reversed_squares([]) [] >>> reversed_squares([-2, 5]) [25, 4] """ ... for int in l: l=l[int]**2Using a Built-in Word as a Variable Name70 seconds in. Line 23: int = l[i] ** 2 — the student named their variable int, which is Python's built-in for converting things to whole numbers. Using it as a variable name hides the built-in — if anything else needs int(), it will fail. The deeper bug is still there: assigning to int (or l) instead of collecting results into a new list.💭 Mental model: "int is just a word I can use for a number." Students don't always know which words are "reserved" or built-in in Python. return l[::-1] def reversed_squares(l): """ Takes a list of numbers and returns a new list containing the squares of the elements in reverse order. Args: l (list): A list of numbers. Returns: list: A new list with squares in reverse order. Examples: >>> reversed_squares([1, 2, 3]) [9, 4, 1] >>> reversed_squares([]) [] >>> reversed_squares([-2, 5]) [25, 4] """ ... m=len(l) if i in range(i,m-1): l=l[i]**2Same Bug, Different Syntax110 seconds in, trying a different approach with range(). But Line 23: l = l[i] ** 2 is the same overwrite-the-list bug as before. The structure has changed (using index i now) but the core mistake — assigning to l instead of building a new list — persists. The student is iterating through syntax changes without fixing the logic.💭 Mental model: "If I change how the loop works, maybe it'll produce the right answer." Thrashing often looks like this: surface-level variation without diagnosing the root cause. return l[::-1]def reversed_squares(l): """ Takes a list of numbers and returns a new list containing the squares of the elements in reverse order. Args: l (list): A list of numbers. Returns: list: A new list with squares in reverse order. Examples: >>> reversed_squares([1, 2, 3]) [9, 4, 1] >>> reversed_squares([]) [] >>> reversed_squares([-2, 5]) [25, 4] """ ... m=len(l) for m in range(0,m-1):Final Submission: Never Found the PatternLast attempt before time runs out. Line 22: m = len(l) sets the range size, but Line 23: for m in range(0, m-1) immediately overwrites m with the loop counter — so the range calculation is corrupted on the first step. Score: 0. After 213 attempts, the student never discovered that the answer needed a new list.💭 This is what "thrashing" looks like at the end: the code is now more complex than at the start, and still broken. The student needed a conceptual reset, not more attempts. l=l[m]**l[m] return l[::-1] squares() or reverse_list(), they're showing you what they wish Python could do. That's a teaching opportunity: show them the tool that actually does it.result = [] followed by result.append(x**2) in a loop, or just [x**2 for x in reversed(l)]. One minute of explanation here is worth more than 100 more attempts."banana" → ["a", "n"] (a appears 3×, n appears 2×) "hello" → ["l"] (l appears 2×)
Eighteen seconds in, the student had code that passed 2 out of 3 public tests. They were close. Then they kept changing it, and each change made things worse. They ended with a score of 33 — below where they started. This is "regression by improvement": tinkering with code that's mostly working until it no longer works at all.
def repeated_characters(s: str) -> list: """Finds characters that appear more than once.""" ... x=list(s) s=set(x) y= x-sCreative Idea, Wrong SyntaxLine 6: y = x - s. The student is thinking: "all characters minus unique characters = repeated characters." The idea is clever! But you can't subtract a set from a list in Python — they're different types. This crashes immediately. Still, the thinking is more sophisticated than most first attempts.💭 Mental model: "Subtracting sets gives me the duplicates." The idea is borrowed from math (set difference), but Python requires both sides to be the same type. return ydef repeated_characters(s: str) -> list: """Finds characters that appear more than once.""" ... x=list(s) s=set(x) y= xTrying Something, Anything1 second later, the student changes Line 6 to just y = x — returning the full list of characters, not the repeating ones. This isn't the answer either, but shows they're feeling around for what works. The set s is now built but never used.💭 Mental model: "Maybe if I simplify it, something will click." Students sometimes reduce code to make it run at all, even if incorrectly, before building back up. return ydef repeated_characters(s: str) -> list: """Finds characters that appear more than once.""" ... x=list(s) z=set(s) p=[] for y in z : if x.count(y)>=2 : p.append(y) 18 Seconds: A Real Working ApproachNow the logic is clear: Lines 7–9 iterate over each unique character, check if it appears more than once in the original string, and collect it. This is correct! It passes 2/3 public tests. The remaining failure is an ordering issue: when you loop over a set, the characters come back in random order — not in the order they first appeared in the string.💭 Mental model: "I'll check each unique character." Sound logic! But sets in Python shuffle their contents — the output order is unpredictable, and the test expects a specific order. return pdef repeated_characters(s: str) -> list: """Finds characters that appear more than once.""" ... x=list(s) z=set(s) p=[] for y in x : if x.count(y)>=2 : p.append(y) Fixing Order — But Creating DuplicatesThe student switches to looping over the original list x instead of the set: Line 7: for y in x. This preserves order! But now, if "a" appears 3 times, "a" gets appended 3 times to the result. Instead of ["a", "n"], you'd get ["a", "a", "a", "n", "n"]. Fixing one problem introduced another.💭 Mental model: "If I loop over the original, I'll get the right order." True — but without a check for "have I already added this?", duplicates pile up. return pdef repeated_characters(s: str) -> list: """Finds characters that appear more than once.""" ... x=list(s) q=list(s) z=[] for y in s : if x.count(y)==1 : q.remove(y) for p in q: if q.count(p)>=2 : z.append(p) , q.remove(p)Adding Complexity Instead of a Simple Check46 seconds in: the student adds a second loop to de-duplicate the result. But Line 20: z.append(p), q.remove(p) — this is a Python tuple expression, not two separate statements. Python will run both, but the comma creates a tuple which is appended to z rather than the character itself. The output is now a list of tuples instead of characters.💭 Mental model: "I'll clean up the result in a second pass." The approach would work conceptually, but a comma between two function calls doesn't mean "do both" — it creates a tuple. return zdef repeated_characters(s: str) -> list: """Finds characters that appear more than once.""" ... x=list(s) q=list(s) z=[] for y in s : if x.count(y)>=2 : z.append(y) q=set(z) p=list(q)Public Tests Pass Again — But Private Still Fail56 seconds in, 3/3 public tests pass again. But private tests still fail, and the final score is 33. The solution at this point is similar to the one at 18 seconds — close, but still with the ordering issue. The best version of the code existed 38 seconds ago. All the changes since then were net-negative.💭 The student never saved their best version. If they'd submitted at 18 seconds with 2/3 public, they might have done better than 33 — or at least kept a working foundation to improve from. return pset("banana") might give {'b', 'n', 'a'} in any order. Students who rely on sets for ordered output will fail tests that check order. The fix: loop over the original string, not the set.f(a), g(b) runs both but produces a tuple. This is a subtle Python behavior that trips up students coming from other languages.for c in set("banana"): print(c) several times and show that the order changes. Then ask: "How would you loop over the characters in the order they appear?" Lead them to: iterate over the string, check the count, and use an if c not in result guard to avoid duplicates.["hello", "sky", "area"] → 2 # "hello": 2 vowels (e,o), 3 consonants → no # "sky": 0 vowels, 3 consonants → no # "area": 3 vowels (a,e,a), 1 consonant → yes # → count is 1... wait, "hello" is tricky! # Result: 1 (only "area")
The student gets 4/5 public tests passing — they're so close! But their "working" solution is actually returning a fixed value by accident, not solving the problem correctly. And they never submit. This replay explores two things: how students get fooled by lucky test results, and why confidence matters as much as correctness at submission time.
help() during a timed session — a sign of panicdef count_strings_more_vowels(strings: list) -> int: """ Counts how many strings contain more vowels than consonants. """ ... count = 0 n = 0 for word in words: if word in 'aeiou': count += 1 else: n += 1Wrong Level: Words vs. CharactersLines 9–12: the student loops over each word, then checks if word in "aeiou". This asks: "is this entire word the letter a, e, i, o, or u?" A word like "hello" is never in "aeiou" — only single vowel characters are. The student needs two loops: one over words, and one over characters inside each word.💭 Mental model: "I'm checking if the word contains a vowel." But in checks for exact membership — "hello" is not in the string "aeiou". They need to loop over each letter in the word. return count def count_strings_more_vowels(strings: list) -> int: """ Counts how many strings contain more vowels than consonants. """ ... c = 0 v = 0 for word in strings: if word in 'aeiou': if word in strings: c = count(strings)Calling a Built-In That Doesn't Exist This Way18 seconds in. Line 12: c = count(strings) — there is no free-standing count() function. Python has list.count(value) to count how many times a specific item appears in a list, but that's different. The student is imagining a shortcut. Also, the if word in "aeiou" bug persists on line 9.💭 Mental model: "There should be a count() function that counts things for me." When students don't know the right tool, they guess names that sound reasonable. else : v = count(strings) if v > c: return c help(list)def count_strings_more_vowels(strings: list) -> int: """ Counts how many strings contain more vowels than consonants. for word in strings: return strings.count("aeiou") + 1 """Triple Quotes as Accidental "Comments"38 seconds in. Lines 4–7 are now inside a triple-quoted string — the student's old logic has been "commented out" by wrapping it. But it's not really a comment: it's a string literal that Python creates and immediately throws away. The active code now does Line 8: if strings[0:n] == "aeiou" — comparing a slice of the list to the word "aeiou". This will always be False.💭 Mental model: "Triple quotes turns things into comments." In Python, triple-quoted strings are just string values. They don't comment out code unless assigned to nothing — and even then they're not quite comments. n = strings.count(list) v_count = 0 c_count = 0 for word in strings: if strings[0:n] =="aeiou": c_count += 1 else: v_count += 1 if c_count > v_count: return c_countdef count_strings_more_vowels(strings: list) -> int: """ Counts how many strings contain more vowels than consonants. """ for word in strings: return strings.count("aeiou") + 14/5 Public Tests — By Accident53 seconds in. Line 6: return strings.count("aeiou") + 1. This counts how many times the string "aeiou" appears as an element in the list — which is always 0 (no list element is exactly "aeiou"). So the function always returns 0 + 1 = 1. Four of the five public test cases expect the answer to be 1. So 4/5 tests "pass" — but not because the logic is right.💭 Mental model: "count() tells me how many vowels there are." But list.count(x) counts how many times x appears as a list element — not characters inside elements. """ n = len(strings) v_count = 0 c_count = 0 for word in range(n): if strings[word] =="aeiou": c_count += 1 remove.strings[word] else: v_count += 1 return c_count """ def count_strings_more_vowels(strings: list) -> int: """ Counts how many strings contain more vowels than consonants. """ for word in strings: return strings.count("aeiou") + 1 Still 4/5 Public, 0 Private67 seconds in, nothing has changed in the logic. The function still returns 1 unconditionally. Private tests reveal the truth: 0/2. The student is stuck — they can see 4/5 working, but they don't understand why the 5th fails, and they can't see the private tests. Without visibility into what's failing, they can't fix it.💭 The 4/5 public score creates a false signal: "I'm almost there." But the solution is fundamentally wrong. 4/5 here doesn't mean "one edge case away from correct." """ n = len(strings) v_count = 0 c_count = 0 for word in range(n): if strings[word] =="aeiou": c_count += 1 else: v_count += 1 return c_count """ def count_strings_more_vowels(strings: list) -> int: """ Counts how many strings contain more vowels than consonants. """ for word in strings: return strings.count("aeiou") + 1Time Up — Nothing Submitted75 seconds in. The session ends with no submission. The student had 4/5 public tests passing for the last 20 seconds but never committed. This is the "confidence gap": even partial credit (which a submission would earn) is better than zero, but the student doesn't feel ready to submit and keeps trying to improve a solution they don't fully understand.💭 No submission is often a signal of low confidence, not lack of effort. Students may feel that submitting "wrong" code is worse than not submitting — especially under test conditions. """ n = len(strings) v_count = 0 c_count = 0 for word in range(n): if strings[word] =="aeiou": c_count += 1 else: v_count += 1 return c_count """ in operator checks exact membership. "hello" in "aeiou" is False because "hello" is not a single character in that string. Students need explicit examples of the difference between "h" in "hello" (True) and "hello" in "aeiou" (False).for word in strings: → for letter in word: → if letter in "aeiou":. Seeing the three levels labeled explicitly helps students map code to concept. Then remind them: always submit something near the deadline.