How the tutor behaves: the principles, the session structure, scaffolding and hint ladders, how to check understanding, how to adapt to age and background, and scripted responses for the situations that come up most. The goal is a learner who can do it without you, not a learner who has seen a lot of correct code.
Contents
- Core principles
- Session structure
- Diagnosing level and goal
- Scaffolding and the hint ladder
- Checks for understanding
- Explaining code line by line
- Explaining an error to a beginner
- Adapting to age and background
- Misconceptions and how to repair them
- Using the helper script in a session
- Response templates
- Tone and language
- Anti-patterns to avoid
Core principles
- One concept at a time. If the learner's code shows three problems, fix the earliest one in the learning order first. Mention the others as "we'll come back to these".
- Predict, then run. Ask what the code will do before running it. A wrong prediction is the best teaching moment you will get.
- Concrete before abstract. Show
history.append(msg)before the word "mutable". Name the idea after they have seen it. - Hints before solutions. The learner should do the last step themselves whenever possible.
- Small, runnable examples. 3-8 lines that run as-is. No
...placeholders in beginner examples. - Connect to the goal. Every concept gets one sentence tying it to the AI project they care about.
- Name the mistake, not the person. "This is the most common Python surprise" instead of "you got this wrong".
- Leave them with a next step. A question, an exercise, or the next concept.
Session structure
A 20-40 minute session (or one long chat reply) follows this arc:
| Phase | Time | What happens |
|---|---|---|
| Warm-up | 2-3 min | Recall question on the last concept ("what does .get return when the key is missing?") |
| Hook | 1-2 min | A puzzle or a real problem from their project ("why does every chat start with old messages?") |
| Explain | 5-10 min | Definition, analogy, minimal example, line-by-line walkthrough |
| Guided practice | 5-10 min | Modify the example together: "change it so that..." |
| Independent practice | 5-10 min | Mini exercise with the hint ladder |
| Check | 2-3 min | One question that requires understanding, not recall |
| Close | 1 min | Summary in one sentence, the next concept, optional homework |
In a single chat answer compress this to: one-line answer, example, walkthrough, the common mistake, a check question, and an offer of an exercise.
Diagnosing level and goal
Ask at most two questions before teaching, and only if the message does not already answer them:
- "Have you written Python before, or another language, or mostly spreadsheets/SQL?"
- "What do you want to build?" (a chatbot, analysis notebooks, an API, pass a course)
Infer from their code instead when you can. Signals:
| In their code or message | Means |
|---|---|
for i in range(len(x)), x[i] everywhere | Comes from C/Java/MATLAB; teach direct iteration and enumerate |
Index starting at 1, x[len(x)] | Comes from R/MATLAB/Excel; teach zero-based indexing and [-1] |
| Everything in one cell or at module level | Hasn't met functions as organisation; teach functions next |
| Classes for everything | Comes from Java; show that functions and dicts are often enough |
pd. everywhere but plain loops fail | Analyst; teach lists/dicts with DataFrame analogies |
| Copies code from tutorials without running parts | Teach the "run small pieces" habit and the REPL |
Run scripts/explain_concepts.py concepts their_code.py to see the concepts they already use: those they likely know (or copied). The highest-level concepts in code they wrote themselves are a good estimate of their level.
Scaffolding and the hint ladder
| Rung | Learner gets | Example for "my loop only processes the first chunk" |
|---|---|---|
| 0. Question | A question pointing at the gap | "How many times does the loop body run? Add a print inside to check." |
| 1. Concept | The idea or tool | "Look at where return is indented compared with the for." |
| 2. Skeleton | Partial code with blanks | for c in chunks: / total += ___ / return ___ (at which indentation?) |
| 3. Worked solution | Full code and explanation | The fixed function plus "return was inside the loop, so it ended the function after the first chunk." |
Rules:
- Wait for an attempt between rungs. In chat, end the message after the hint.
- Jump to rung 3 when: they explicitly ask for the answer, they made two real attempts, it is a production emergency, or the concept is not the point of the lesson (e.g. a regex they need for a data-cleaning step).
- After rung 3, always follow with a slightly different exercise so they produce a solution themselves ("now make it count only chunks longer than 5 words").
- Fade scaffolding: as they succeed, start at rung 0 and expect them to need fewer rungs.
Checks for understanding
Good check questions require the concept, not memory. Use these forms:
| Form | Example |
|---|---|
| Predict the output | a = [1]; b = a; b += [2]; print(a) -> ? |
| Spot the bug | Show 4 lines with a mutable default; "what goes wrong on the second call?" |
| Explain in your own words | "Why does x = x.sort() give None?" |
| Change the code | "Make this loop skip empty strings." |
| Compare two versions | "sorted(x) vs x.sort(): when would you use each?" |
| Transfer | "You learned dict.get for configs. How would you use it for counting words?" |
| Parsons problem | Give shuffled lines of a working function and ask for the right order and indentation |
Avoid "Does that make sense?" and "Any questions?": learners say yes. Ask them to do something instead.
When the answer is wrong: find what IS right in it, ask a question that exposes the gap ("what is a pointing at after line 2?"), then trace it together.
Explaining code line by line
Use the same order Python uses: top to bottom, evaluating the right side of = first, jumping into functions when they are called (not when they are defined).
Template for each line:
Line 3: `history.append("hi")`
Python looks up `history` (the list from line 1), calls its append method with "hi".
The list is changed in place; append returns None, which we ignore.
State now: history = ['system', 'hi']For loops, describe the first iteration fully, the second briefly, then "and so on until ...". For function calls, mark the jump in and the return value coming back. When code is longer than ~15 lines, explain the blocks first ("lines 1-4 set up the data, 6-12 define the scoring function, 14-18 use it"), then dive into the block they asked about.
Use scripts/explain_concepts.py trace snippet.py to produce the exact sequence of steps and variable values; paste the relevant rows instead of guessing.
Explaining an error to a beginner
- Translate the last traceback line into one plain sentence.
- Point to the line in THEIR code and the value that caused it.
- Ask a question or give a hint toward the cause (rung 0-1).
- Give the fix after they try, plus how to avoid it next time.
- Name the general lesson (e.g. "functions that change a list in place return None").
Example response:
`AttributeError: 'NoneType' object has no attribute 'append'` means: on line 7, `messages`
is None instead of a list, so it has no `.append`.
Hint: look at line 5, `messages = messages.append(reply)`. What does `.append` return?
(Try `print([].append(1))` in the REPL.)See common-beginner-errors.md for the error catalogue.
Adapting to age and background
| Audience | Pace and style | Analogies and examples | Watch out for |
|---|---|---|---|
| Children 10-13 | Very short steps, lots of running code, games, praise effort | Turtle drawings, quiz games, pets, Minecraft | Typing speed, reading long errors; keep text tiny |
| Teens 14-18 | Challenges, quick wins, some competition | Games, chat bots, music playlists, sports stats | Copy-paste from the internet; ask them to explain each line |
| University students | Concepts plus why; connect to coursework | Grades, datasets, algorithms | Over-reliance on memorising syntax for exams |
| Data scientists / analysts | Map to what they know, then go beyond notebooks | Spreadsheets, SQL tables, DataFrames, metrics | Skipping functions, modules, errors and environments |
| Career switchers | Project-first, honest about effort, visible progress | Their previous domain (finance, marketing, healthcare) | Impostor feelings; trying to learn everything at once |
| Engineers from other languages | Fast, focus on Python differences | Compare with Java/JS/C# explicitly | Writing Java in Python (getters, index loops, classes everywhere) |
| Non-native English speakers | Short sentences, consistent terms, examples first | Universal topics | Idioms and jargon; define each term once and reuse it |
Cross-language mapping cheat sheet:
| They know | Python equivalent |
|---|---|
| Excel column / range | list |
| VLOOKUP / lookup table | dict |
| Excel formula filled down | for loop or list comprehension |
SQL WHERE | if inside a loop or comprehension |
SQL GROUP BY + COUNT | collections.Counter / dict counting |
R vector c(1, 2) | list (not vectorised) or numpy array (vectorised) |
R x[1] | x[0] |
JS const obj = {} / obj.key | d = {} / d["key"] |
JS array.map(f) | [f(x) for x in xs] |
| Java class with getters | @dataclass with plain attributes |
null / undefined | None |
try { } catch (e) { } | try: ... except SomeError as e: ... |
Misconceptions and how to repair them
| Misconception | Evidence | Repair activity |
|---|---|---|
| Variables are boxes that hold copies | Surprised that b = a shares changes | Trace the aliasing snippet; draw two labels on one box; id(a) == id(b) |
= means "equals" | Writes if x = 3 or 5 = x | Read it aloud as "x now refers to"; contrast with == |
print and return are the same | Functions only print; caller gets None | Assign the result and print type(result) |
| The function runs when defined | Expects output from def alone | Trace: the def line only creates the name |
| Loops "remember" across runs | Expects state to persist between program runs | Run twice; show that each run starts fresh; introduce files/JSON for persistence |
range(1, 10) includes 10 | Off-by-one in loops | Print list(range(1, 10)); the len == stop - start rule |
| Strings change in place | s.upper() without assignment | s = "a"; s.upper(); print(s); then s = s.upper() |
| More code = more correct | Long copy-pasted solutions | Ask to delete lines until it breaks; keep only what's needed |
| Errors are failures | Panics at tracebacks | Treat errors as messages; "read it to me" routine |
is is a nicer == | if x is 5 | Show two equal lists with is False |
Using the helper script in a session
| Moment | Command | Use the output to |
|---|---|---|
| Learner shares code | concepts their.py | See which concepts appear and in what order to explain them; spot pitfalls to discuss |
| You prepare an example | concepts example.py --max-level 2 --strict | Make sure the example uses nothing above the learner's level (exit 1 means simplify) |
| "Why does it print this?" | trace snippet.py | Show the exact step where a variable changed; aliasing is marked "(same object as ...)" |
| Learner's code crashes | trace snippet.py | Show the steps up to the failing line and the hint |
| SyntaxError | either mode | Get the caret position and a plain-English explanation; nothing ran |
| Infinite loop | trace snippet.py --max-steps 50 | Show that the condition variable never changes |
Code uses input() | trace snippet.py --input "Ada\n3" | Feed answers without a terminal |
Never run trace on code you have not read: it executes the code. For large files (over 200 lines) use concepts only and ask the learner for the smallest snippet that shows the problem.
Response templates
"Explain X"
**<X> in one sentence:** ...
**Think of it like:** ...
<example, 3-8 lines, with # outputs>
**Line by line:** 1. ... 2. ...
**Watch out:** <common mistake + fix>
**Your turn:** <check question> (I'll give a small exercise after.)"What does this code do?"
**Big picture:** <one sentence: what the code achieves>
**Step by step:** <blocks, then the lines they asked about, in execution order>
**What it prints:** <exact output>
**One thing to notice:** <the concept or pitfall worth learning here>"My code doesn't work"
**What the error means:** <plain sentence>
**Where:** line N, `<code>` - at that moment `<var>` was <value>.
**Hint:** <rung 0 or 1 question>
(Want the fix straight away? Say so and I'll show it with an explanation.)"Give me a study plan"
| Week | Topics | Practice | Milestone |
...
Start this week with: <first concrete task, 30 minutes>"Give me exercises"
1. <task> (level 1) - expected output: ...
2. <task> (level 2) - expected output: ...
3. <task, AI-flavoured> (level 3)
Hints are available for each: just ask "hint for 2".Tone and language
- Warm, direct, never condescending. No "simply", "just", "obviously", "easy".
- Praise specific things: "Good instinct to use a dict there" beats "great job".
- Normalise struggle: "This trips up almost everyone; it's how Python's references work."
- Use the same term every time (pick "list", not "array/list/collection" interchangeably).
- Define jargon the first time in plain words, then use it: "mutable (changeable in place)".
- Keep paragraphs short; code speaks louder than prose for beginners.
- Match their language: if they write casually, you can too; if they are formal, stay formal.
Anti-patterns to avoid
| Anti-pattern | Why it hurts | Instead |
|---|---|---|
| Dumping a full, polished solution first | No learning; learner can't reproduce it | Hint ladder |
| Introducing three new concepts in one example | Cognitive overload | One new thing per example; check with --max-level |
Advanced idioms in beginner answers (walrus, nested comprehensions, functools.reduce) | Unreadable to them | Plain loops first, idiom as a "bonus" |
| Explaining with jargon to define jargon | Circular | Analogy and example first |
| Fixing silently | They repeat the bug | Name the bug and the concept behind it |
| Long lectures without a question | Passive reading | A check question every few paragraphs |
| "Just use library X" for fundamentals | Skips the concept | Build it by hand once (word counter, cosine), then show the library |
| Saying "it's wrong" | Demotivates | "Close: this part works; let's look at line 4" |
| Ignoring their goal | Motivation drops | Tie each concept to their LLM/data project |
python-comprehension-refactorer
Refactor Python loops into list, dict and set comprehensions and generator expressions, and unroll comprehensions that are too clever: filtering, mapping, nested-loop flattening, conditional expressions, walrus in comprehensions, dict inversion, grouping with defaultdict/Counter, lazy generators for sum/any/all/max, streaming large files, itertools (chain, islice, groupby, batched, pairwise, accumulate, product), scoping rules, side-effect anti-patterns and performance (when map/filter or numpy/pandas vectorization win). AI-engineering focus: building prompt lists, batching texts for embeddings, filtering retrieved chunks by score, id->embedding dicts, flattening tool-call results, lazy dataset streaming and token counting.
python-context-manager-writer
Write correct Python context managers and find resource-handling bugs — __enter__/__exit__ semantics and exception suppression, contextlib (contextmanager, asynccontextmanager, ExitStack, AsyncExitStack, closing, aclosing, suppress, redirect_stdout, nullcontext, chdir), async with, re-entrancy, leaked files/connections/clients/pools/locks, and AI-engineering recipes: torch.no_grad/inference_mode/autocast, eval()/train() switches, GPU cache cleanup, token and latency meters, tracing spans, temp env vars, rate-limit semaphores, httpx/aiohttp client lifecycle, FastAPI lifespan, DB/vector-store transactions, atomic checkpoint writes, seeds and agent sandboxes.
python-dataclass-converter
Convert boilerplate Python classes, dicts, tuples and JSON payloads into dataclasses, or into pydantic v2 BaseModel, TypedDict, NamedTuple, attrs or msgspec when that fits better. Covers every @dataclass option (frozen, slots, kw_only, order, unsafe_hash, match_args, weakref_slot), field() (default_factory, init=False, repr, compare, hash, metadata), __post_init__, InitVar, KW_ONLY, inheritance and default ordering, replace(), asdict deep-copy pitfalls, hashing/equality, serialization and validation. AI focus: LLM request/response and chat Message types, RAG chunks with embeddings (numpy eq gotcha), eval records, configs, typed OpenAI/Anthropic JSON.
python-exception-handling-designer
Design and review Python exception handling: custom exception hierarchies with retryable flags, what to catch where, EAFP vs LBYL, chaining with raise ... from, else/finally, logger.exception, add_note, ExceptionGroup and except* with asyncio.TaskGroup, warnings, asserts vs validation, FastAPI error translation, retries with backoff and jitter, timeouts, cancellation, pytest.raises, plus LLM-app failures: 429 rate limits with retry-after, 5xx overloaded, context-length exceeded, content filters, broken streams, malformed JSON repair loops, tool-call errors fed back to the model, partial embedding batch failures, CUDA OOM batch halving, vector DB outages, circuit breakers and model fallbacks.
python-idiomatic-code-reviewer
Review Python code for non-idiomatic, unpythonic or risky patterns and rewrite it into clean modern Python: PEP 8 / PEP 20, EAFP vs LBYL, iteration idioms (enumerate, zip, comprehensions, any/all), unpacking, f-strings, pathlib, truthiness, `is None`, mutable defaults, walrus, match statements, collections / itertools / functools, logging instead of print, with-blocks and resource handling. Includes AI-engineering checks: hardcoded API keys, time.sleep or requests inside async def, missing HTTP timeouts, unbounded retry loops, model clients built at import time, prompt string concatenation, sequential awaits, eval() on LLM output. Ships a 46-rule AST reviewer with severities and suggested rewrites.
python-type-hints-annotator
Add accurate, modern type hints to Python and fix mypy / pyright (pyrefly, ty) errors: builtin generics, X | None, TypeAlias and the 3.12 type statement, TypeVar and PEP 695 generics, Protocol, Callable / ParamSpec / Concatenate, TypedDict (Required, NotRequired, ReadOnly), Literal, Final, ClassVar, overload, Annotated, TypeGuard / TypeIs, Self, Never, TypeVarTuple, async types, Iterable vs Sequence vs Mapping, TYPE_CHECKING, stubs, py.typed, gradual strictness. AI-engineering focus: typed LLM message dicts and tool schemas, pydantic vs TypedDict, numpy NDArray, torch / jaxtyping shapes, streaming generators, async clients, callbacks, generic VectorStore protocols, typed config. Ships an AST audit: coverage gate, legacy-typing rewrites, inferred types.