Open or download your current resume
The site includes your uploaded document so visitors can access it immediately while browsing your page.
A clean portfolio-style home page with direct access to resume, interests, research, and blog content. This layout includes image placeholders, layered motion, and smooth scroll reveals so you can keep the structure polished while adding your own details over time.
Each card below jumps directly to a main section so visitors can immediately browse your academic profile, current interests, research direction, and writing.
Keep your experience, education, achievements, and downloadable resume in one structured place.
Open section → 02 / InterestsHighlight favorite topics, communities, skills, and extracurricular activities.
Open section → 03 / ResearchShowcase themes, papers, experiments, labs, or long-form ideas with room for visuals.
Open section → 04 / BlogCreate a home for essays, updates, notes, and future posts with a magazine-style layout.
Open section →This area is ready for your formal achievements and also links directly to the resume file already placed in the folder.
The site includes your uploaded document so visitors can access it immediately while browsing your page.
Add degree information, institution, coursework, and milestones here.
Summarize impactful technical work, publications, or thesis directions in concise language.
Include organizations, mentoring, student communities, or outreach contributions.
Use this card to describe the role, scope, methods used, and measurable outcomes.
List languages, frameworks, research methods, software tools, and domain strengths.
Use this section to show personality, long-term interests, interdisciplinary themes, and the communities or ideas you care about.
Highlight student groups, volunteering, outreach, hackathons, or communities where you contribute and learn.
This section is structured for projects, publications, experiments, or thesis-oriented work, with visual placeholders and room for concise summaries.
Add a short overview of the problem area, why it matters, and the methods or frameworks you are using to investigate it.
Use this for a publication summary, a capstone project, or a lab collaboration.
Describe a system, prototype, or experiment setup with key findings or lessons.
Outline the next question you want to pursue or the extension of current work.
First-hand accounts from projects, hackathons, and learning experiences.
No posts in this category yet.
A look back at everything since that first shaky Zoom call in November: what's actually been covered, what's changed about how I teach, and why this is nowhere near finished.
If statements this week, plus a room that felt different from the start: more returning faces, a few new ones, and more hands going up than usual. I forgot to plan an else statement and improvised with the example already on screen. Then a student's roller coaster exercise broke in three different ways at once, and fixing it together turned into the best debugging lesson we've had yet.
I finished assembling the first complete draft of the manuscript today — Introduction, Methods, Results, Discussion, and Conclusion, all in one document, for the first time since I started this project fourteen months ago. It's rough. There are sentences I'll rewrite, a couple of figures that need to be redone at higher resolution, and at least one Methods paragraph Joe already flagged as too dense. But it's a real draft of a real paper, built entirely from work I did and understood, and I want to take a minute to look back at how it got here before diving into revisions.
A mixed review session that was supposed to take forty minutes and took the entire hour and then some, because every topic we touched pulled in a question from something covered months earlier. A good problem to have, and a real lesson about pacing reviews.
After day two ran into more material than an hour could hold, I rebuilt the lesson around just four related ideas: booleans, comparison operators, and, and or. The slower pace changed the room: more volunteers, more students explaining things to each other, and a third grader who worked out and vs. or on her own.
Booleans, if statements, for loops, and while loops: four big ideas for students who'd only just started learning to code. Attendance held steady from day one, but participation dropped as the pace outran the class. Here's what that taught me about the difference between covering material and actually teaching it.
A full breakthrough session: everyone builds their own number guessing game from scratch, no template this time, combining nearly a year of scattered lessons into one program each kid can genuinely call their own.
Standard deviation, introduced conceptually rather than with the formula, because nobody in this group needs to compute one by hand yet and everybody benefits from understanding what it's actually pointing at.
Past the mini project from the trip, into something a little bigger: a simple calculator program that pulls together nearly everything covered so far. Notes on lesson planning for a group that's outgrown single-concept sessions.
The Discussion section has taken longer to draft than Methods and Results combined, and I think that's because it's the section where I actually have to take a position rather than just report what happened. It's easy to describe a permutation importance table. It's harder to write a paragraph honestly explaining what it means that clinical features account for the overwhelming majority of predictive power, without either overselling the speech features' contribution or undermining the motivation for the entire project.
Twelve students joined our first Google Meet coding session. We installed Python, covered data types, variables, and user input, and worked through two exercises together. By the end, everyone had working code. Here's how the first class went.
I started a cross-communal coding initiative bringing together students from Avondale, The Heights at Westridge, and Ridgeview at Panther Creek. The first meeting had nobody knowing each other and experience levels ranging from zero to two-plus years. Here's what actually happened—and why it felt like the right kind of start.
First Zoom call since returning from Hyderabad, and it felt stranger than I expected to be back behind a laptop camera with kids I'd spent two weeks sitting across a whiteboard from. A reflection on what changed and what, oddly, didn't.
The last in-person session before I fly home. A mini project, a full classroom of kids who feel entirely different to me than they did two weeks ago, and a goodbye I'd been quietly dreading since about day three of actually being here.
A low-stakes review day, in person, that turned out to be the best gauge yet of what's actually landed over seven months. Also: the funniest wrong answer of the entire series, courtesy of Naveen.
The hardest day of the trip so far. A lesson that needed translating almost line by line, a power outage in the middle of it, and a real, unglamorous lesson in adapting on the fly rather than sticking to the plan.
A full group activity day: splitting into teams, using real cricket scores the kids tracked themselves, and watching mean, median, and mode stop being abstract vocabulary and start being something they argued about.
Working one-on-one with a student who's said maybe fifty words to me total since November. In person, away from the group, something finally opened up, and it wasn't at all what I expected the reason to be.
First real lesson taught in person, and my first genuine encounter with what "limited resources" means in practice: one chalkboard, a handful of chalk pieces, and no way to quietly delete a mistake the way Zoom's screen share always let me.
The day I stopped teaching through a screen. Landing in Hyderabad, arriving at the children's home in Kompally, and meeting, in person, nine kids I'd only ever known as small rectangles on a laptop. Nothing about it went quite how I'd pictured it, and all of it was better.
Spent this stretch actually writing prose for the first time, starting with Methods and Results rather than the Introduction, on Joe's advice — apparently it's common to draft these sections first since they're the most mechanical and the least dependent on getting the framing exactly right, and writing them first tends to clarify what the Introduction and Discussion actually need to set up and explain.
The last Zoom session before I fly to Hyderabad. Lists, quickly, and then most of the session spent on logistics, nerves, and a curriculum recap that made five and a half months feel a lot longer than it does week to week.
Probability, taught through coin flips and dice rolls that turned into an actual experiment when theory and reality didn't quite match up. A good lesson in why expected results and actual results aren't always the same thing.
For loops, and the specific moment a program stopped feeling like a list of instructions and started feeling like a tool that could do work on its own. One of the cleanest breakthroughs of the whole series so far.
First real statistics session: mean, median, and mode, taught using something more concrete than a textbook data set. A question about which measure is "the right one" led somewhere I didn't expect.
While loops, introduced correctly and then immediately broken on purpose, because watching a program get stuck forever taught the lesson faster than any explanation of the word "condition" ever could.
A reflection session that turned into genuinely exciting news: I've booked a trip to Hyderabad in May, which means these classes are about to become, briefly, something other than a Zoom call for the first time since we started.
Almost a full year after I first started reading about domains, tasks, targets, and data, I sat down this week to start turning all of this into an actual manuscript, and immediately ran into a problem I hadn't anticipated: I don't really know how to write a research paper. I know how to run experiments, keep notes, and argue with myself about whether a result is real. I don't have much practice putting that into the specific, conventionalized shape a paper is supposed to take.
Nested conditionals, the first topic this year that genuinely made the whole group slow down at the same time. A session about layers, about drawing boxes inside boxes, and about admitting a topic is hard rather than pretending it isn't.
Putting trigonometry to actual use, and picking an example rooted in something the kids already do for fun rather than a textbook word problem about ladders leaning against walls.
Else statements, which should have been a quick add-on to last time's lesson. Instead it turned into the funniest session in weeks, once Naveen decided to test the limits of what counts as a valid else block.
First real trigonometry session, and I underestimated how much of the difficulty isn't the math itself but the sheer number of new words arriving at once: hypotenuse, adjacent, opposite, sine, cosine, tangent, all in one sitting.
If statements, the first real branch point in anything we've built so far. I planned one explanation and ended up using three different versions of it before it actually landed for everyone, which is its own kind of lesson.
I set aside a patient-grouped held-out test set this week — a slice of patients that took no part whatsoever in feature selection, hyperparameter tuning, or fold-ensemble training. Everything up to this point has been evaluated purely through cross-validation, which is a reasonable way to make modeling decisions on a small dataset but leaves open the possibility that repeated tuning decisions, made by me, looking at CV scores over and over across months, have quietly overfit to the CV folds themselves. A genuinely untouched test set is the only way to check that.
A step back from syntax entirely: a session on basic computer literacy, because I realized a few weeks in that some of the group could write working code without a clear picture of what a file, a folder, or the computer itself was actually doing underneath it.
Booleans are supposed to be the simple topic, just two values. Tonight proved that simple doesn't mean quick, especially once comparison operators entered the mix and every question started with "wait, is that true or false."
I wanted to connect algebra to something visual before we went any further, so tonight was about graphing intuition: what a line actually represents, and the moment a student realized a graph is just a picture of an equation, not a separate thing to memorize.
Most of this stretch was incremental tuning, and in the spirit of documenting the real process rather than just the wins, I want to write down what didn't work as much as what did, because the list of rejected ideas ended up being long.
Arithmetic operators should be the easiest session of the whole curriculum. It mostly was, until the modulo operator showed up and turned into the most requested topic of the night, for reasons I did not see coming.
A whole session eaten by screen-sharing problems before we even got to the actual lesson: integers versus floats, and why Python cares about the difference even when a person wouldn't.
A session on inequalities turned into a session on keeping four different attention spans pointed at the same whiteboard-that-isn't-a-whiteboard. Notes on pacing, side conversations, and a number line that finally made things click.
Ran the ablation study I've been planning since November, comparing four feature configurations under identical patient-grouped 5-fold cross-validation: linguistic (handcrafted) features alone, SBERT embeddings alone, clinical features alone, and combinations of all three. The results settle the question from a few weeks ago, at least as far as this dataset can settle it.
Text seemed like the easy topic. It was not the easy topic. Indexing broke everyone's brain a little, myself included when I tried to explain why counting starts at zero without just saying "because it does."
Back after the break, and instead of jumping straight into new material I spent this session taking stock: how many classes we've actually held, who's stuck around, and what I got wrong in the first stretch that I want to fix in the second.
Our first real math session, and my first time teaching algebra to someone who has never seen an equals sign used the way I mean it. A session about balance, about why you do the same thing to both sides, and about switching gears entirely for a week.
With the entry-MMSE bug fixed and the rest of the clinical feature joins audited and confirmed correct, I ran a proper permutation-importance analysis across all five cross-validation folds. The method: for each fold, take the trained model, measure its validation R², then shuffle one feature column at a time (breaking that feature's relationship to the target while leaving everything else intact) and measure how much R² drops. A bigger drop means the model was relying on that feature more. Repeating the shuffle multiple times per fold and averaging cuts down on noise from any single unlucky shuffle.
I introduced input() and immediately regretted not thinking through what happens when you hand five kids a program that asks them anything they want to type. Mostly it was fine. One answer nearly broke the lesson plan in the best way.
No laptop, no code, just twenty minutes of logic puzzles because I wanted to see how these kids think before I taught them one more rule to follow. It turned into one of the more fun sessions so far, and taught me something about who's actually leading in this class.
While setting up the ablation study I'd planned last time, I went digging into why the "entry MMSE" feature — which I expected, based on everything I understand about this problem, to be by far the single most predictive feature available — wasn't behaving the way I expected in early feature-importance checks. It turned out to be almost inert, which didn't make sense.
Variables sound simple until a nine-year-old asks why the box doesn't disappear after you read what's inside it. A session about labels, boxes, and the specific question that made me rethink how I explain assignment.
Getting Python running on one shared laptop should have been simple. It was not simple. A walkthrough about installers, PATH variables, and the particular chaos of teaching software setup over a connection that drops every few minutes.
With patient-grouped cross-validation now in place as an honest baseline, I finally did the thing I'd been putting off since August: incorporated the clinical metadata that comes bundled with the Pitt Corpus alongside the speech data. Specifically, I added each patient's entry MMSE (their cognitive score at their first recorded visit), Blessed Dementia Scale score (a measure of functional impairment in daily activities), CDR (Clinical Dementia Rating), NYU and Mattis battery scores, and their baseline diagnosis category (dementia, control, or otherwise).
I've wanted to volunteer teach for a while, and it finally became real on a Sunday evening: five kids gathered around one shared laptop at a children's home in Kompally, and a Zoom link that took three tries to actually connect. Here's how it started.
I flagged this concern a couple weeks ago and finally sat down to check it properly, and it turned out to be a real problem, not a false alarm. My cross-validation setup had been splitting the dataset at the level of individual samples (patient-visits), not at the level of patients. Since many patients in this cohort have multiple visits, that means it was entirely possible — and, once I checked, actually happening — for one visit from a given patient to land in the training fold while another visit from that same patient landed in the validation fold.
Switched the modeling backbone from Random Forest to LightGBM, a gradient-boosted decision tree library. The core difference from Random Forest is how the trees are built: Random Forest trains many trees independently on bootstrapped samples and averages them, while gradient boosting trains trees sequentially, where each new tree is fit to correct the errors (residuals) of the ensemble built so far. In principle, this lets the model capture more subtle feature interactions and generally squeezes more performance out of tabular data — at the cost of being more sensitive to hyperparameters and more prone to overfitting if you're not careful.
With the Random Forest baseline in place, I spent this stretch trying to squeeze more signal out of the longitudinal structure of the data rather than adding entirely new feature families. The instinct behind this: I've been treating each visit somewhat independently, but the whole point of longitudinal data is that a patient's history should inform the prediction, not just their most recent snapshot.
I trained a Random Forest regressor on the same feature set the LSTM had access to — handcrafted discourse features plus SBERT embeddings, flattened per patient rather than fed in as a sequence — and it outperformed the LSTM by a meaningful margin, landing around R² ≈ 0.4 on cross-validation. That's still far from a result I'd call strong, but it's a real improvement over the neural network's ~0.44 validation score once you account for the fact that the LSTM's number came from a single validation split while the Random Forest number is a cross-validated estimate, and the Random Forest got there with a small fraction of the tuning effort.
Over four intense days, our team built ShopSmart—an app that finds the cheapest, fastest, or most eco-friendly multi-store shopping route. We placed first out of all submissions, earning $100 and five .xyz domains. Click to read the full story.
After weeks of feature engineering, I finally trained a model. Since the whole premise of this project involves patients with multiple visits over time, a sequence model felt like the obvious first choice — I built an LSTM that takes a patient's visit history as a sequence of feature vectors (handcrafted coherence features plus SBERT embeddings per visit) and predicts the MMSE score at a future visit.
I lost most of two days this week to environment setup, which is not something I expected to be writing about in a research blog, but it felt worth documenting because it's such a normal part of doing this kind of work. Coreference resolution — figuring out that "she," "the woman," and "her" in a transcript all refer to the same entity — needs a fairly heavy transformer-based model, and getting a working coreference pipeline installed alongside the rest of my existing packages caused enough dependency conflicts that I ended up isolating it into its own virtual environment entirely, separate from the environment I use for everything else. Not elegant, but it works, and I'd rather have a slightly ugly setup than lose another day to it.
Handcrafted lexical-overlap features (how many words two consecutive sentences share) are a reasonable first pass at coherence, but they have an obvious blind spot: two sentences can be about the exact same thing while sharing almost no vocabulary at all ("the woman is drying dishes" vs. "she's wiping plates with a towel"). If I only measure literal word overlap, I'll systematically miss semantic coherence — coherence at the level of meaning rather than surface form.
This week was almost entirely plumbing, and I mean that in the least glamorous sense possible: getting raw transcript files and a metadata spreadsheet into a single clean table I can actually build features on top of. It is not exciting to write about, but it's the part of research nobody tells you takes as long as it does.
Dataset search this week, and I think I found the right one: the DementiaBank Pitt Corpus. It's a collection of transcribed speech samples from participants completing a handful of standardized elicitation tasks — most notably the "Cookie Theft" picture description task, along with fluency, recall, and sentence-repetition tasks — collected from both a dementia group and a healthy control group. Critically for what I want to do, many participants were seen across multiple visits over time, with cognitive assessment scores (including MMSE) recorded at each visit.
This week was pure reading, and it was the first time the project started to feel like it had real technical bones rather than just a motivating story. "Discourse coherence" turns out to be a whole subfield with its own vocabulary, and a lot of it maps surprisingly cleanly onto the kinds of breakdowns that get described anecdotally in dementia case studies.
I started meeting regularly with Joe Xiao, a PhD student who agreed to mentor me on this project, and that alone has changed the pace of things. Having someone to push back on half-formed ideas in real time is worth more than another week of solo reading. This week's conversations helped me go from "healthcare NLP, broadly" to something much more specific.
I've decided to spend this year trying to do real machine learning research, and I want to write about it as it happens rather than only after the fact. I have no formal research experience — until now, "research" for me has meant reading other people's papers, not writing my own. So the first few weeks of this project have mostly been about learning how research problems get made in the first place, not about writing any code.
I've wanted to do this for close to a year, in the vague, guilty way you want to do something you keep not scheduling. Sometime in October, a family friend put me in touch with Swathi, who helps run day-to-day activities at a children's home in the Alwal / Kompally area of Hyderabad. I explained what I could offer: I'm not a teacher, I've never run a classroom, but I can teach Python reasonably well and I'm decent at math, and I'd like to do that for kids who might not otherwise get exposure to either. She said to send a Zoom link and she'd get some kids in front of a screen. That was basically the whole planning process.
The home has one laptop that gets used for calls like this, plus whatever phones the older kids have access to. Swathi told me ahead of time not to expect a polished setup, and she was right to warn me. When the call connected, twenty minutes later than planned because of a shaky WiFi signal, I was looking at four kids clustered around a single small screen, with a fifth leaning in from just outside frame. Their names, as best I caught them over a connection that kept dropping words: Divya, Kiran, Naveen, and Sneha, with Rahul hovering at the edge.
Roughly what I was looking at for that first call, minus the connection dropping twice mid-sentence.
I didn't have a lesson plan for day one, on purpose. I wanted the first session to be about figuring out who was on the other end of the call before I figured out what to teach them. So I asked questions: how old are you, what grade, have you ever used a computer for anything besides watching videos, do you like math or does it feel like a chore. Divya, who I'd guess is around twelve, answered every question before anyone else could, which told me something useful right away. Kiran barely spoke. Naveen made a joke I didn't fully catch through the audio lag, and the other three laughed, so I decided that was a good sign rather than something to worry about.
Swathi translated a few things back and forth when English wasn't landing, which I expect will keep happening for a while. Most of the kids have some school-level English, but it's clearly a second or third language for most of them, and technical vocabulary is going to be its own separate hurdle on top of that. I made a mental note not to assume a word is understood just because nobody says otherwise.
Swathi gave me a quick, informal picture of the place while we waited for the last WiFi hiccup to sort itself out. Around twenty children live there full time, ranging from very young to nearly finished with school, with a rotating handful of caretakers and volunteers keeping things running day to day. Not all twenty were on tonight's call, obviously, just whoever was around, interested, and could fit in front of the one working camera. I don't know yet how that group will settle out week to week, whether it'll be the same five or six kids or a slowly shifting cast depending on who's free on a given Sunday. I decided not to worry about that tonight and just teach whoever showed up.
I also asked, mostly out of my own curiosity, how kids usually end up spending a Sunday evening there when there isn't a Zoom call. The answer was ordinary in a way I found reassuring: homework, chores, cricket in whatever open space is available, arguments over whose turn it is to use the one working charger. It's not a dramatically different picture from any group of kids anywhere, which I think is exactly the point, and exactly why teaching here felt like something worth doing rather than something worth performing.
I picked Python for the same reasons I'd pick it for anyone starting from zero: the syntax reads close to plain English, the error messages are relatively forgiving, and there's a real, tangible sense of building something within the first few sessions rather than weeks of abstract setup. I also picked it because it's what I know well enough to teach without constantly doubting myself, which matters more than people give it credit for when you're already nervous about everything else.
I didn't install anything today. I didn't want the first session to be lost to pip errors and PATH variables on a shared laptop I've never seen the specs of. Instead we talked, generally, about what a computer actually does when you tell it to do something, using examples that had nothing to do with code: a recipe is a set of instructions, a cricket scorer follows rules to decide what counts, a locked phone either accepts your passcode or it doesn't. I wanted "instructions, followed exactly, in order" to feel familiar before it had to carry any syntax.
Two things stood out enough to write down. First, the connection is going to be a constant variable I don't control, and I need to plan lessons that survive a frozen screen or a dropped call without losing the thread entirely. Second, Kiran didn't say more than a few words the whole session, and I don't yet know if that's shyness, a language gap, or just a first-day thing. I'm not going to push. I'd rather spend a few weeks finding out than assume an answer.
Swathi and I agreed on Sunday evenings, her time, going forward. Next week: installing Python and getting everyone to print something on the screen for the first time. It's a small goal. After tonight, it feels like the right sized one.
I keep coming back to how little of tonight was actually about teaching. Maybe fifteen minutes out of the whole call touched on anything resembling a lesson. The rest was just getting oriented: to the connection, to five new names and faces, to a setup I don't fully understand yet and won't for a while. I think that's fine. I'd rather spend a first session getting the human part right and the teaching part slightly late, than the other way around. I don't know exactly what this is going to look like in six months. I know I want to find out.
I budgeted twenty minutes for installing Python. It took fifty, and that's with me doing most of the clicking myself over a screen share, because asking someone in Kompally to read out a Windows installer dialog to me, translated through Swathi, one checkbox at a time, was slower than I expected in every possible way.
The plan looked this simple on paper. It was not this simple on the actual laptop.
The shared laptop is older than I'd guessed, running a version of Windows I haven't personally touched in years, and it took two attempts because the first installer download stalled out at around 60% and never recovered. On the second try it finished, and then the classic beginner trap showed up right on schedule: nobody had checked "Add Python to PATH" during setup, so opening a terminal and typing python --version returned nothing but an error about an unrecognized command. I've hit this exact wall with American students plenty of times. It's oddly comforting how universal it is.
Kiran, who said maybe six words total during our first session last week, spent tonight's installer wait quietly reading every line of the setup wizard out loud to himself, in a low voice I could only half hear over the call. Not asking questions, not performing understanding for anyone, just working through it at his own pace. I decided not to interrupt that, even during the long dead stretches waiting for a progress bar to move. Some kids need me talking constantly to stay engaged. He very clearly didn't.
I don't think I'd fully appreciated, before tonight, how much of "learning to code" for a total beginner is actually "learning to sit through your computer doing confusing things and not panicking." We reinstalled, checked the box this time, and I had Naveen read the terminal output back to me line by line so I could tell him what mattered and what he could ignore. That turned into a decent exercise on its own: most of what a terminal prints at you is noise, and figuring out which line actually matters is a skill, not a given.
Divya got hers working fastest, mostly because she asked to try it on her own phone using a Python app instead of waiting for the shared laptop, which hadn't occurred to me as an option. I told her that was a smart workaround and meant it. It also means going forward I might have two or three kids working from phones and a couple sharing the laptop, which changes how I think about screen-based demonstrations. I'll need to describe what's happening out loud more than I'd planned, since not everyone will be looking at the same screen at the same time.
Once Python was actually running, the reward for fifty minutes of installer chaos was one line: print("Namaste, world!"). I asked each kid to change the text to something about themselves before running it again, which is the same exercise I've used with every beginner I've ever taught, because it works. Divya printed her name and age. Kiran, who I'd worried about after last week's quiet first session, printed "I like cricket" without being asked to add anything extra, and that felt like a small, real thing.
Next time I install anything again, I'm sending Swathi a written, numbered list ahead of time so she can start the download before the call even connects. Fifty minutes of dead air watching a progress bar is fifty minutes I'd rather spend teaching. Lesson noted, mistake made once, hopefully not twice.
One more thing worth writing down: everyone left tonight's call with Python actually running on something they can get back to, whether that's the shared laptop or a phone. That's a small, boring, entirely necessary milestone, and I don't want to undersell it just because it wasn't glamorous. A lot of people who try to teach themselves to code quit during exactly this stage, alone, with no one to read a confusing error message out loud to. Nobody here had to sit with that confusion by themselves tonight, and I think that's worth more than whatever time it cost us.
Attendance was six tonight, up from five, which I'm choosing to read as a good sign rather than a coincidence. A new kid, Priya, joined partway through, sitting just behind Sneha and mostly watching rather than participating, which is exactly how I'd expect a first session to go for anyone dropped into a class already three weeks in.
I used the explanation I always use for variables: think of it as a labeled box. You write a name on the box, you put something inside it, and later you can look inside the box just by using its name. age = 11 is a box labeled age with the number 11 inside it. It's not a perfect metaphor, but it's a working one, and it usually survives first contact with actual code.
Where it got interesting was Sneha's question, which I didn't have a crisp answer for on the spot: if I put something in the box, and then look inside it, does the box empty out? It's a genuinely reasonable question if your only prior experience with "storing" something is physical, like putting a toy in a container and taking it back out. I told her no, the box keeps what's inside until you specifically put something new in it or the program ends, and reading it doesn't remove it. She tried it herself, printing the same variable three times in a row, watching the same value come back each time, and seemed satisfied only once she'd verified it firsthand rather than taken my word for it.
That's a pattern I want to encourage rather than smooth over. I'd rather a student not believe me until they've checked than accept an explanation just because I said it with confidence.
Naveen, in the meantime, had already moved past the box metaphor entirely and started calling variables "nicknames," which isn't technically what I said but isn't wrong either, and might honestly be a cleaner way to think about it for some people. A box implies a physical container with limited space. A nickname just implies a name that refers to something. I didn't correct him. I don't think there's only one right metaphor here, and his version seemed to be working fine for him.
The natural next step was showing that a variable can change: assign age = 11, print it, then assign age = 12 and print it again. Divya got there before I finished explaining, typing ahead of the group and asking whether that meant the "old" 11 was gone forever. Yes, I said, unless you'd saved it somewhere else first. That led to a short, slightly chaotic tangent about whether you could have two boxes with the same number in them, which yes, obviously, but explaining why that's obvious to someone brand new to the idea took longer than I expected.
I checked in with Priya near the end and asked if any of it made sense so far. She said yes, mostly, in a tone that suggested "mostly" was doing a lot of work in that sentence. I'm not going to slow the whole group down to re-teach variables for one new student, but I told her she could message Swathi with questions between sessions, and Swathi could relay them to me. Whether that actually happens is a different question. I'll find out next week whether "mostly" needs another pass.
It occurred to me on the walk away from my own laptop afterward that this is probably going to keep happening, kids joining at different points, catching different fractions of the foundation depending on when they arrive. I don't have a real plan for that yet beyond leaning on kids like Divya to informally catch newcomers up. I'm going to keep an eye on whether that's sustainable once the gap between "week one" and "week however many" gets bigger than three weeks.
I opened tonight with no laptop involved at all, which surprised the group enough that Naveen asked, half-joking, if I'd forgotten to prepare. I told him this was the preparation. Programming is mostly logical thinking wearing a syntax costume, and I wanted a session that stripped the costume off entirely for once.
I gave them the classic river-crossing riddle: a farmer needs to get a fox, a chicken, and a bag of grain across a river using a boat that only holds him and one item at a time. Left alone, the fox eats the chicken, and the chicken eats the grain. How does he get everything across safely? I didn't expect anyone to solve it immediately, and nobody did, but the way the group worked through it told me more about how they think than any coding exercise has so far.
Divya tried to brute-force it, listing combinations out loud faster than she could track them. Kiran, who I'm slowly learning thinks before speaking rather than not thinking at all, sat quietly for almost two full minutes and then said, correctly, that the trick was taking something back across the river, which nobody else had considered. That was the answer. He'd essentially found the solution in his head before anyone else had a framework for it.
I've spent three sessions gently wondering whether Kiran's quietness was shyness, a language barrier, or disengagement, and tonight ruled out the third option pretty definitively. He's not behind. He's just not going to blurt out a half-formed guess to fill silence the way Divya or Naveen will, and I think I've been unconsciously reading "speaks less" as "following less," which isn't the same thing at all. I want to be more careful about that going forward, and specifically make space for him to answer without having to compete for the loudest voice in the room.
I asked him afterward, gently, how he'd gotten to the answer so fast. He said he just imagined actually being the farmer, standing at the river, and worked out what he personally would try first. That's a more grounded way of approaching a logic puzzle than most adults manage, in my experience, and I told him so. He shrugged it off like it was obvious, which, to him, it apparently was.
Divya and Naveen solved a second riddle, about a man who lies on odd days and tells the truth on even days, faster than I expected, so I let them argue it out between themselves while I worked through the river crossing again with Sneha and Priya at a slower pace. Having two puzzles at different difficulty levels running at once, informally, worked better than I'd have guessed. I'm going to plan for that split more deliberately going forward instead of stumbling into it by accident.
No code tonight, and I don't think that was a wasted session. If anything, watching how they reasoned through a problem with no syntax to hide behind gave me a better sense of where to put the emphasis once we're back to Python next week.
I'm going to try to work a short puzzle like this into roughly one out of every four or five sessions going forward, not as a break from real learning but as its own kind of real learning. If a student can hold three constraints in their head and reason forward from them, whether that student is picturing a boat and a riverbank or an if statement and a loop, the underlying muscle is the same one. I'd rather build that muscle deliberately than assume the coding sessions alone will do it.
Up to now, every program we've written has just displayed things at the kids. Tonight I introduced input(), which means a program can finally ask a question and do something with whatever comes back. I underestimated how much that single change would shift the energy of the room.
The starter example was simple: ask for a name, print a greeting back. I ran it myself first to show what it looked like, typed "Deeptanshu" when prompted, and the program said hello to me. Fine, expected, boring even. Then I let Naveen try it on the shared laptop, and instead of typing his name, he typed "your worst nightmare," pressed enter, and watched the program cheerfully print "Hello, your worst nightmare !" back at him. The whole call lost it laughing, myself included, and it took a solid minute to get everyone back on track.
I want to be honest that I could have shut that down and stayed strictly on script. I didn't, because it was a genuinely good, harmless demonstration of something worth knowing: the program has no idea what a name is supposed to look like. It takes whatever text arrives and treats it exactly the same way regardless of whether it makes sense to a human reading it. That's a real, useful idea about how input works, and Naveen basically taught it to the group better than my slide would have.
Once the laughing died down, I asked the group what they thought would happen if he'd typed nothing at all and just pressed enter. A few guesses came in: an error, a blank space, the program refusing to continue. We tried it, and the answer is that Python is perfectly happy to accept an empty string as a valid answer and print "Hello, !" without complaint. That surprised a few people more than Naveen's joke had, which I found genuinely interesting. A silly answer read as silly. An empty answer being accepted without a fight read as somehow more unsettling, like the program should have known better and didn't.
After that, I made sure everyone got to run the program with their own input, one at a time, partly because it's good practice and partly because nobody was going to sit still through someone else's turn without wanting one of their own. Kiran typed his actual name, no games, which fits the pattern I'm noticing: he participates fully, just without needing an audience reaction to do it.
Priya, still finding her footing three weeks in, typed her name correctly on the first try and grinned when it worked, which was a nice moment to watch. Small wins matter more early on than I think students realize in the moment, and more than I sometimes remember to point out.
We closed by combining input with variables from two weeks ago: ask for a name, store it, ask for a favorite subject, store that too, then print both back in a single sentence. It's not a complicated program, but watching five kids realize their two most recent lessons fit together into something slightly more real was worth the earlier chaos. Next week, I want to get into strings properly, though after tonight I'm tempted to build in a five-minute buffer for whatever unplanned demonstration shows up on its own.
Looking back at five sessions now, this is the first one where the room's energy came from the material itself rather than from me trying to generate it. I didn't have to work to keep anyone's attention tonight. The program asking a question and talking back did that on its own. I'd underestimated how much of teaching total beginners is really just finding the moment where the thing you're teaching becomes interesting enough to carry itself, and getting out of the way once it does.
Tonight was the first session that wasn't Python at all. Swathi had mentioned a few weeks ago that most of the kids are shakier in math than reading level would suggest, particularly algebra, which apparently gets taught in their schools more by memorized procedure than by any real explanation of why the procedure works. I decided to take a full week off from code and start on equations instead.
I didn't start with "move the number to the other side," which is how I suspect most of them have been taught, if they've been taught this at all. Instead I used a balance scale: an equation is two things that weigh the same amount, and whatever you do to one side, you have to do to the other, or the scale tips and it's no longer true. 3x + 2 = x + 14 means both sides are equal right now, and every step we take is really just simplifying without breaking that balance.
The framing I used tonight: not a set of steps to memorize, but two sides that have to stay equal the whole way through.
Subtracting x from both sides, then subtracting 2 from both sides, then dividing by 2, walked us to x = 6, and I had Divya check it by plugging 6 back into the original equation on both sides to confirm they actually matched. That check matters more than the solving, in my opinion. A lot of students, in any country, learn to move symbols around without ever confirming the answer means anything, and I don't want that habit forming here from week one.
Naveen kept wanting to solve for x by guessing and checking rather than using the balance method, which actually worked for the easier equations and completely fell apart once I gave them one with fractions involved. That was a useful moment: guessing works until it doesn't, and the balance method works every time, even when it's slower. I didn't frame it as "your way is wrong," more as "here's when each tool is actually useful," which felt like the more honest framing anyway.
Sneha asked why we even bother finding x at all, which is a completely fair question I don't think I answered especially well in the moment. I gave her a "real world" example about splitting a cost evenly, which landed okay but felt a little canned. I want a better answer ready before this comes up again, because it will.
Kiran, working through a harder equation near the end, made an arithmetic slip rather than a conceptual one, subtracting 2 from one side but writing down the wrong result. What struck me was that he caught it himself, mid-explanation, backing up and correcting it out loud without any prompting from me. That's a different kind of confidence than getting the right answer immediately, and arguably a more useful one going forward: noticing your own mistake before someone else has to point it out.
This is our last session before a short break. Swathi mentioned the home does something for the winter holidays, and I don't want to compete with that or assume everyone has consistent internet access over the break anyway. We'll pick back up in January. Six weeks in, I'm already thinking about how to structure the return so it doesn't feel like starting over.
Before we signed off, I asked everyone to just say one thing they'd learned since November, no particular order, no pressure to be profound about it. The answers were small and specific in a way I liked: Divya said print statements, because it was the first thing that ever actually worked. Naveen said modulo, even though we haven't covered it yet, apparently because Sneha had mentioned it existed and he'd gone looking it up on his own out of curiosity. That last one, a kid getting curious enough to look ahead of the curriculum without being asked, is the kind of small detail I want to remember on the weeks when progress feels slow.
Three weeks off is longer than I expected it to feel. I spent a chunk of the break wondering whether attendance would hold up after a gap, whether the shared laptop would still work, whether Swathi's own schedule would still allow for Sunday evenings going forward. All reasonable things to worry about with a volunteer arrangement that has no contract behind it, just a mutual agreement to keep showing up.
Everyone came back. Six of the original kids plus Priya, who joined partway through and has apparently been quietly practicing without me knowing, based on how comfortable she looked typing tonight compared to three weeks ago. I didn't plan a new lesson for this session. I wanted to take stock instead, both for them and for myself.
I ran a loose, ungraded review: variables, input, and the balance-scale approach to equations, mixed together in a few small examples. The Python side held up well. Everyone remembered what a variable was without prompting, and most of them could explain input in their own words rather than just reciting my explanation back. The algebra side was shakier. Naveen reverted to guess-and-check on an equation that needed the balance method, and I don't think that's really his fault. One session on a new topic, followed by a three-week gap, isn't much to build permanence on. I should have expected some erosion and I did, but seeing it in practice still stung a little.
Divya, unprompted, mentioned that she'd kept practicing print statements and variables on her phone over the break, just typing small programs into a Python app with nothing in particular to build, purely because she wanted something to do. I don't think I fully appreciated, until she said that out loud, how much of this project's actual success depends on things happening entirely outside the one hour I'm present for. I can teach a concept once a week. Whether it survives depends almost entirely on what happens in between, which is mostly out of my hands and, apparently, sometimes happening anyway without me asking for it.
Writing this series has made me more honest with myself than I think I'd be otherwise, so here's an actual mistake: I moved through the first few Python topics faster than I should have, because five or six kids nodding along felt like understanding, and it wasn't always. Tonight's review made that visible. I'm going to slow the pace slightly going forward and build in more of these low-stakes recap sessions between new topics, rather than treating review as something that only happens if there's spare time left over.
I sketched out, roughly, where I want this to go over the next few months: finish the core Python fundamentals, keep interleaving algebra and eventually some trigonometry and statistics, and see how far we get. I'm resisting the urge to plan the whole year in detail, because the last two months have already taught me that the plan bends around the reality of a shared laptop, unreliable WiFi, and kids who are, understandably, still feeling out whether this is something worth showing up for every single week. So far, they keep showing up. That's the only metric I really trust right now.
I also spent a few minutes tonight just asking Swathi how she thought things were going, from her side of the room rather than mine. She said the kids talk about the class during the week sometimes, which I hadn't known and found more encouraging than almost anything that happened during the actual review. I can measure whether a concept stuck. I can't measure whether something mattered to someone once the call ends, and hearing, secondhand, that it apparently does, is the kind of thing I'm going to hold onto on the weeks that feel like more of a grind than a joy.
I expected strings to be one of the gentler topics, mostly because everyone already understood text as a concept before tonight. What I underestimated was how strange indexing looks the first time you see it, especially the part where counting starts at zero instead of one.
name[0] returning the first letter of a string, not the second, was the single biggest sticking point of the night. Sneha asked, reasonably, why it isn't just name[1] for the first letter, since that's how counting works everywhere else in her life. I gave her the honest answer I had, which is that it's really about distance from the start rather than position: the first letter is zero steps from the beginning, the second letter is one step from the beginning, and so on. I'm not sure that fully landed. I watched her nod in the way that means "I'll accept this for now" rather than "that makes sense," and I don't blame her. It took me longer than I'd like to admit to internalize this myself, years ago.
We drilled it with examples rather than more explanation, which worked better than talking around it further. Print the first letter, then the third, then the last one using -1, which is its own separate strange idea but landed faster than zero-indexing did, oddly enough. By the fourth or fifth example, Divya was predicting the output correctly before running the code, which is exactly the sign I look for that something's actually sinking in rather than just being copied.
len() and .upper() went smoothly by comparison, mostly because they don't require the same mental shift. I let everyone run .upper() on their own names, which turned into a small amount of chaos because Naveen immediately tried to see if he could shout in code by uppercasing an entire sentence, and honestly, that's a completely reasonable thing to want to try.
Sneha, still turning zero-indexing over in her head even after the drills, asked whether len() counts starting from zero too, expecting some kind of consistency between the two ideas. It doesn't, and I had to admit that Python is being slightly inconsistent here on purpose: len("Kiran") returns 5, a normal count, while indexing into the string still starts at 0. I don't think there's a way to make that feel less arbitrary to a beginner. I just told her plainly that some of this is a convention to memorize rather than a rule to derive, and that's an okay thing to say sometimes rather than forcing a deeper logic that isn't really there.
Kiran tried to access name[10] on a five-letter name and got an IndexError, and instead of treating it as a mistake to move past quickly, I stopped and had the group figure out together why it happened. The string only has five letters, positions zero through four, so position ten doesn't exist. It's a small thing, but I want errors like this to register as informative rather than embarrassing, and pausing on them instead of rushing past seems like the right way to build that habit early.
I ended the session by telling the group, honestly, that indexing is one of those ideas that clicks properly a few weeks later rather than in the same session you first meet it, and that feeling shaky about it tonight doesn't mean anything's wrong. I said this partly for Sneha's benefit and partly because I remember it being true for me, years ago, and I'd rather set that expectation now than let anyone quietly assume they're the only one still confused.
Tonight was inequalities, building directly off the equation-solving from before the break. The math itself went fine. Managing eight kids' worth of attention on a single shared screen, several of them clearly still catching up on each other's news from the break, was the harder part of the evening.
I framed inequalities as almost identical to equations, solved the same way, except the answer isn't one number, it's a whole range of numbers. x + 3 > 7 solves the same way as an equation would, subtracting 3 from both sides, landing on x > 4. The difference is that 5, 6, 100, and 4.001 are all valid answers, not just one specific value.
This is the picture that made it click for most of the group: not one answer, but everything past a certain point.
The number line is what actually made it land. I drew it live, marking 4 with an open circle since it's not included, then shading everything to the right of it. Priya asked why 4 itself doesn't count if the inequality is basically "about" 4, which is a sharp question. I explained that x > 4 means strictly more than 4, and if we wanted 4 included we'd write x ≥ 4 instead, with a filled-in circle rather than an open one. Small symbol, meaningfully different meaning. She got it once I drew both versions side by side.
Where I struggled more was pacing for a group that's clearly not all in the same place. Divya and Sneha were ready to move to compound inequalities, two conditions at once, within about ten minutes. Naveen and a newer kid, Rahul, who's joined in the last couple weeks, were still working through the basic version. I split my attention back and forth more than I'd like, and I don't think either group got my full focus for very long at any point.
I don't have a clean fix for this yet. Assigning practice problems and circulating, the way I would in an actual physical classroom, doesn't translate cleanly to one shared laptop and a Zoom call. I'm going to think about whether having faster students explain a step to a slower student, out loud, might do double duty: keeps the quick ones engaged, gives the slower ones a second explanation in different words than mine.
At one point I genuinely lost track of which sub-group I'd last checked in on, and gave the same explanation twice to Divya's group by mistake while Naveen's group sat waiting, visibly restless, for their turn. Nobody complained, but I noticed it, and it's exactly the kind of small inefficiency that compounds over a full session into lost time and drifting attention. A physical classroom lets you glance around a room in half a second. A single video call genuinely doesn't offer the same peripheral awareness, and I don't think I've fully adjusted to that difference yet, three months in.
Worth noting: Rahul is new enough that he's missing both the Python fundamentals and the algebra we did in December. I'm not going to hold the group back to catch him up, but I asked Divya, who seems to enjoy explaining things almost as much as she enjoys being first with an answer, whether she'd be willing to give him a quick recap before next session. She said yes immediately. I'll see next week whether that actually happens or whether I need a more structured plan for kids who join mid-stream.
Rahul himself seems mostly undeterred by being behind, which I was glad to see. Toward the end of the call he asked, a little sheepishly, whether it was okay that he didn't understand everything yet. I told him that's true of literally everyone in the room on some topic or another, myself included on plenty of things outside of math and code, and that showing up confused is still showing up. He seemed to relax a little after that. I want this class to be a place where admitting confusion out loud is normal, not a place where everyone's quietly performing understanding they don't actually have.
Tonight's actual lesson took maybe fifteen minutes. Getting to the point where I could teach it took twenty five, because the shared laptop's screen-sharing kept freezing on a single frame while audio kept playing, which is a uniquely disorienting way to lose a class's attention. I'd talk for a sentence or two before someone would say "the screen is stuck" and I'd have to stop, restart the share, and figure out where we'd actually left off visually versus where I thought we'd left off.
Once we were finally moving, the topic was integers versus floats: whole numbers versus numbers with decimal points, and why Python treats them as genuinely different types rather than just "numbers." I ran type(7) and type(7.5) side by side to show int and float printed back, which is a small enough distinction that a couple of the kids visibly wondered why it was worth an entire session.
I answered that with a demonstration rather than an explanation: dividing two integers that don't split evenly, 7 / 2, and showing it returns 3.5, a float, even though both inputs were whole numbers. Sneha's reaction was somewhere between confused and mildly offended, which is honestly the correct reaction the first time you see a language automatically change what kind of number you're holding. I like when a demonstration does more convincing than I could do with words alone.
Divya pushed further and asked what happens with 8 / 2, where the division actually does come out even. It still returns a float, 4.0, not the integer 4, which surprised her more than the 7 divided by 2 example had. I think that's because 3.5 makes intuitive sense as "not a whole number," while 4.0 looks like it's pretending to be something it doesn't need to be. I explained that Python's regular division always produces a float regardless of whether the result happens to be whole, and that there's a separate operator, double slash, for division that deliberately drops the decimal and stays an integer. I didn't go deep on that tonight. I just wanted the seed planted for later.
Naveen ran into a real, useful error on his own: he tried to add a number typed through input() directly to another number, without converting it with int() first, and got a TypeError about not being able to combine a string and an integer. We hadn't officially covered this yet, since input() was several weeks back and I hadn't reinforced that it always returns text, but the error message itself, read out loud and puzzled over as a group, taught the lesson better than if I'd pre-empted it with a warning. He won't forget that input() gives you text now. I'm fairly confident of that.
I'm going to ask Swathi whether there's any way to test the laptop's screen-sharing setup before class starts rather than during it, even if that just means logging on ten minutes early together. Losing a quarter of the session to a frozen screen is avoidable friction I'd rather not keep paying for every few weeks.
It's easy, writing these posts after the fact, to make the technical problems sound like minor color around the real lesson. In the moment, twenty five minutes of a sixty minute class lost to a frozen screen doesn't feel minor at all. It feels like almost half the session. I don't want to sand that down. This is genuinely a constraint I'm teaching around every single week, not an occasional inconvenience, and I think being honest about that is part of what makes this record useful rather than just a highlight reel.
After two sessions eaten partly by technical problems, tonight was refreshingly straightforward. Arithmetic operators: addition, subtraction, multiplication, division, and modulo. Everyone already understands the first three from ordinary school math, so most of the session was really just showing that Python's symbols match what they already know, plus one new idea at the end.
Modulo, %, giving back the remainder of a division, was new to literally everyone, myself having to explain it from scratch rather than build on prior knowledge. I used 7 % 2 equals 1, since 7 divided by 2 is 3 with 1 left over. Divya asked what it's actually useful for, which is a fair challenge to any new concept, and I gave the classic answer: checking whether a number is even or odd, since number % 2 is 0 for even numbers and 1 for odd ones.
That single example turned into ten minutes of everyone testing random numbers against % 2, which I hadn't planned for but was happy to let run. Naveen figured out on his own that he could check divisibility by any number this way, not just 2, and tested whether his age was divisible by 3 without me suggesting it. That's the kind of extension I want to see happen unprompted, even when it derails the planned pace a little.
I mentioned that back in December, Naveen had apparently looked up modulo on his own after Sneha name-dropped it, and tonight I got to watch that curiosity pay off directly: he was noticeably faster than the rest of the group at internalizing what the operator actually does, since he'd already half-encountered it outside of class. I don't think I can manufacture that kind of self-directed head start on demand, but I can keep dropping the names of upcoming topics early and see who goes looking.
We spent the back half on a handful of examples mixing operators together, like 2 + 3 * 4, to confirm that Python follows the same order-of-operations rules everyone already learned in school. This went by quickly and easily, which I appreciated after the last two sessions. Sometimes the win is just a topic that doesn't fight you.
Priya asked whether Python would ever get order of operations "wrong" by accident, in the sense of a bug in the language itself rather than a mistake in what someone typed. I told her no, the rules are fixed and consistent, which is actually one of the more reassuring things about a programming language compared to, say, a calculator with a specific quirky button layout. The computer isn't guessing at what you meant. It's applying the exact same rule every single time, which is precisely why predicting output before running code is a skill worth building early.
I checked back in on whether Divya had walked Rahul through the basics she'd promised a couple weeks ago. She had, apparently over a phone call outside of class time, which is more initiative than I expected and genuinely touched me a little. Rahul kept pace with tonight's material better than he had with inequalities, which I'll credit at least partly to that. I want to find a way to formally encourage that kind of peer teaching going forward, rather than leaving it to chance and Divya's goodwill.
I asked Divya directly what she'd actually told him, mostly out of curiosity about how she'd compress a month of material into a phone call. She said she just went through the "boxes" explanation for variables and the balance-scale idea for equations, in her own words, skipping most of the extra detail I'd have included. Hearing my own lessons filtered back through someone else's summary was useful in an unexpected way: it told me which parts of each explanation are actually load-bearing, and which parts are just me talking longer than necessary.
Everything we've done in algebra so far has stayed entirely symbolic: equations, inequalities, numbers and letters on a screen. Tonight I wanted to connect that to something visual, because I think a lot of the abstraction that makes math intimidating comes from never seeing what any of it actually represents.
I picked a simple relationship, y = 0.5x + 0.4, and rather than explaining slope and intercept as vocabulary first, I had the group plug in values for x and compute y, one point at a time. x = 0 gives y = 0.4. x = 1 gives y = 0.9. We plotted each point as we found it, and by the fourth or fifth point, Sneha said, almost to herself, "oh, it's just going up the same amount each time," before I'd said the word "slope" at all.
Built one point at a time rather than dropped on screen fully formed, which seemed to matter.
That's exactly the order I wanted things to happen in: notice the pattern first, then get handed the word for it. I gave her "slope" once she'd already found the idea behind it, and I think that ordering makes the vocabulary stick better than introducing the term and hoping the meaning catches up later.
Once we had "slope" on the table, I asked what would happen to the picture if the number in front of x got bigger, say from 0.5 to 2. Rahul guessed the line would get steeper before we actually tried it, which I hadn't expected from him specifically, given how much he's still catching up on from missing the first couple months. We tested it and he was right, and I made a point of telling him so in front of the group, plainly, without over-praising it into something awkward.
The bigger moment came when Priya asked whether the graph and the equation were "the same thing" or "two different things that match up." I told her they're the same thing, just described two different ways, and watched something visibly shift in how she was looking at the screen. A line isn't a separate object you memorize facts about. It's a picture of exactly what the equation says, nothing more mysterious than that. I don't think I've ever had a student ask that question quite that directly before, and I'm glad she did, because the answer feels like one of the more genuinely useful things I've said in this whole series so far.
Naveen, to be fair, spent a good chunk of the session more interested in what happens if you use a negative slope, which led to a line going the other direction and a brief, fun detour about what "negative growth" even means in a real situation. I let it run for a few minutes before pulling us back. Curiosity that arrives slightly off-topic is still curiosity, and I'd rather feed it than shut it down every time it doesn't match my plan exactly.
I closed by connecting tonight back to Python, briefly, pointing out that a for loop stepping through numbers one at a time, something we haven't covered yet but will soon, is basically the same process we just did by hand: plug in a value, get a result, move to the next value. Nobody needed to understand that fully tonight. I just wanted the seed planted that these two subjects, which I keep teaching in alternating weeks like separate things, are going to keep quietly rhyming with each other the further we get.
Looking back at tonight, I think it's the first algebra session that actually felt collaborative rather than me demonstrating and the group following along a step behind. Building the line point by point, with everyone contributing a value and a guess before I confirmed it, meant the picture belonged to the group by the time it was finished rather than arriving pre-made. I want more sessions built this way going forward, math especially, since it's easy to default to lecturing through a topic I already understand deeply instead of constructing it together the slower way.
Booleans are the topic I usually expect to move through fastest, since there are only two possible values and most people have an intuitive sense of true and false before any code enters the picture. Tonight took longer than I planned, mostly in a good way.
I framed each comparison operator as a question the computer is answering with either True or False. age > 10 is really asking "is age greater than 10," and the program answers honestly based on whatever's actually stored in age. Sneha picked this up fast, faster than I expected, and started making up her own comparisons to test before I'd finished the planned examples. That's usually my favorite sign that something's landing well: a student running ahead of the lesson rather than waiting to be told what to try next.
The genuinely hard part, and I should have seen this coming after teaching plenty of American beginners the same lesson, was the difference between = for assignment and == for comparison. age = 10 stores a value. age == 10 asks a question and gets back True or False. They look almost identical and mean completely different things, and mixing them up is one of the single most common beginner mistakes in any language, not just Python.
Naveen mixed them up almost immediately, writing if age = 10 instead of if age == 10, which actually produces a syntax error in Python rather than silently doing the wrong thing, unlike some other languages. I told him that was a lucky break, in a sense: Python is refusing to let this specific mistake pass quietly, and that's a kindness even though it doesn't feel like one when you're staring at an error message.
I told the group a quick, true story about myself to drive the point home: years ago, in a language that doesn't protect against this the way Python does, I once spent most of an afternoon debugging a program that kept doing the wrong thing, only to eventually find a single stray = where I'd meant ==. Sneha found it very funny that I, supposedly the person who knows what he's doing, had made exactly the mistake we were laughing at Naveen for. I think that landed better as a teaching moment than any amount of me warning them to be careful would have.
Worth noting, since I've written about Kiran's quietness a few times now: tonight he answered five comparison questions in a row correctly, out loud, unprompted, faster than anyone else typed their answers. I don't think "quiet" and "confident" are opposites, and I'm increasingly convinced I read too much into his early silence back in November. He just doesn't narrate his thinking the way some of the others do. The thinking's clearly there regardless.
Booleans and comparisons set up everything coming next: if statements, which is the first time these kids' programs will actually make a decision rather than just running top to bottom. I'm looking forward to that shift more than almost any other topic so far.
I closed tonight by asking the group to predict, without me explaining anything yet, what they thought an "if statement" might do based purely on the name. Priya guessed it would make the program ask a question and then choose what to do next, which is close enough to correct that I almost skipped ahead and taught it on the spot. I held off. I'd rather she arrive at that same answer again next week, this time backed by actual code, than have tonight's lucky guess stand in for real understanding.
I also spent a couple minutes tonight just having everyone write out five different True and False statements about themselves, nothing to do with code at all, just as a way of getting comfortable with the vocabulary before it shows up wrapped in syntax next week. "I am older than ten" is True. "I have a pet elephant" is False, courtesy of Naveen, who apparently thinks the exercise is funnier if the false statements are absurd rather than merely incorrect. I'm fine with that. Absurd is still memorable, and memorable is most of what I'm actually optimizing for at this stage.
I noticed something a couple sessions ago that I let sit for a while before acting on it: a few of the kids can write short working programs but seemed hazy on more basic computer concepts underneath that, like what a file actually is, where saved work goes, or what "running" a program technically means as opposed to just clicking something. Tonight I paused the Python curriculum entirely to fill that in.
I built the session around a simple four-step picture: you type instructions, the computer reads them one at a time, the computer does exactly what you wrote, and you see the result. No magic, no interpretation beyond exactly what's on the screen. I used the print statements from our very first sessions as the example, walking through each step slowly enough that it felt almost too obvious to say out loud.
That plainness was the point. Priya said afterward that she'd sort of assumed the computer was "figuring out" what she meant, the way a person would, rather than doing exactly and only what the instructions said. That's a really common and reasonable assumption for a beginner to carry, and it's also exactly backwards from how computers actually work, which is why bugs happen: the computer isn't misunderstanding you, it's doing precisely what you told it, even when what you told it wasn't what you meant.
We spent the second half on files and folders on the shared laptop specifically: where their saved Python files actually live, what a file extension is, why .py matters. This felt almost too basic to include in a coding class, and a year ago I might have skipped it as obvious. It clearly wasn't obvious to everyone, based on how many questions came up once I actually opened the file browser and showed it rather than assuming it as background knowledge.
Naveen asked, at one point, whether the computer was "thinking" while it ran a program, which is the exact opposite misconception from Priya's, and just as reasonable. I told him no, not thinking in the way a person thinks, just following instructions extremely fast, and that the appearance of intelligence is really just speed and precision dressed up to look clever. He seemed a little disappointed by that answer, in the same way I think a lot of people are quietly disappointed the first time they really sit with what a computer actually is. I don't think that disappointment is a bad sign. I think it's the beginning of a more accurate picture replacing a comforting but wrong one.
I don't think this was time wasted on non-coding content. If anything, I think a chunk of the confusion I've half-noticed over the last few months, students who can follow along step by step but seem lost about the bigger picture, traces back to gaps like this one that I'd been assuming were already filled in. I want to build in more of these foundational check-ins going forward, rather than only reacting once I notice something's off. It's easy to assume a class understands the plumbing just because they can follow the recipe.
Swathi mentioned afterward that most of the group's school computer exposure has been limited to shared lab sessions with strict, narrow instructions, rarely any open-ended exploring of how the machine actually works underneath. That context helped explain tonight's confusion better than I'd have guessed on my own. It's not that anyone was behind or inattentive. It's that nobody had ever really been invited to poke around and ask "wait, what is this actually doing" before. I'd like this class to be the place where that kind of question is not just allowed but expected.
Everything up to tonight has been booleans and comparisons as things a program can evaluate and print. Tonight was the payoff: if statements, where a program actually changes what it does based on the answer, rather than just telling you the answer and moving on regardless.
My first pass framed an if statement as a question with a consequence: if the condition is True, the indented lines run. If it's False, they don't. That's technically complete and it's the explanation I usually lead with everywhere I teach this. It landed fine for Divya and Sneha, who've been ahead of the group's pace for a while now, and visibly didn't land for Naveen and Rahul, who both nodded in the way I've learned means "I heard the words."
I tried again with a physical example: a locked door only opens if you have the right key. The condition is "do you have the key," and the door opening is the indented block running. No key, no opening, nothing happens, the program just moves on. This got Naveen there. Rahul was still uncertain.
The version that finally landed for Rahul came from Kiran, not from me. He offered, unprompted, that it's like a cricket umpire: if the ball hits the stumps, it's out, and if it doesn't, play just continues like nothing happened. Rahul plays cricket, apparently far more than he codes, and that framing clicked instantly in a way neither of my two explanations had. I've said before in this series that I want to keep adapting explanations to whoever's in front of me, and tonight was a reminder that sometimes the best version of an explanation isn't going to come from me at all. I'm keeping the umpire example in my back pocket for next time this topic comes up with someone new.
What struck me most was that Kiran offered this without being asked to help, which is new. Back in December, during the river-crossing riddle, he solved things quietly and let others discover it. Tonight he actively jumped in to rescue an explanation that wasn't working for someone else. That's a meaningfully different kind of participation than anything I'd seen from him before, and I don't think it happened by accident. Four months of slowly building comfort in this group seem to have added up to something.
We closed with a prediction exercise: I'd show an if statement and a value, and everyone had to guess True or False, and what would print, before I actually ran it. This is one of my favorite exercises regardless of which group I'm teaching, because it forces a mental walkthrough instead of just watching output happen passively. By the fourth example, even Rahul was calling out answers before I hit run, correctly, which told me the umpire explanation had actually done its job rather than just sounding good in the moment.
I want to remember this session mainly for the reminder it gave me about where good explanations actually come from. I walked in with one solid, tested metaphor and assumed it would be enough for everyone. It wasn't, and the fix wasn't a better metaphor from me, it was making room for someone else's. I don't think I'll ever fully stop needing that reminder, no matter how many times a session like tonight delivers it.
Tonight was the first proper trigonometry session, and I went in slightly nervous, because trig was the math topic I remember struggling with most myself at their age, and I wasn't sure how well that memory would translate into actually teaching it better than however I was taught.
The math itself, ratios of sides in a right triangle, isn't conceptually harder than anything we've covered. What makes it hard on day one is vocabulary density: hypotenuse, adjacent, opposite, sine, cosine, tangent, all landing in the same twenty minutes. I watched Priya's expression go from following along to visibly overwhelmed somewhere around the fourth new term, and I don't think that's a failure on her part. That's just a lot of unfamiliar words to hold onto at once, especially in a second language.
Drawn live, slowly, one label at a time, rather than presented all at once.
I slowed down and rebuilt the triangle from scratch, live, labeling one side at a time rather than showing the finished diagram up front. Hypotenuse first, since it's the easiest to identify (always across from the right angle). Then adjacent and opposite relative to whichever angle we cared about, with a concrete emphasis that these two labels actually change depending on which angle you're looking from, which I think is the detail that trips people up longest.
Rather than presenting sine, cosine, and tangent as things to memorize, I tried to frame them as just ratios, fractions comparing two sides, the same way "half" is a ratio comparing one thing to two things. sin = opposite / hypotenuse is a fraction, nothing more mystical than that. Sneha asked why these three specific ratios get special names instead of just calling them fractions, which is a genuinely good question I answered honestly: because they show up constantly in real problems, often enough that giving them names saves everyone from re-deriving the same fraction repeatedly.
I tried a memory trick a teacher once gave me, SOH-CAH-TOA, sine is opposite over hypotenuse, cosine is adjacent over hypotenuse, tangent is opposite over adjacent, and immediately regretted it slightly, because it added one more unfamiliar string of letters to a night already overloaded with unfamiliar words. Divya latched onto it anyway and started repeating it under her breath while working through examples, which seemed to genuinely help her even if it added to the pile for everyone else. I think the lesson there is that no single memory aid works equally well for every student, and I should probably offer a couple of different ones rather than assuming one trick fits the whole room.
I didn't get to applying these ratios to solve for missing sides tonight, and I made a deliberate choice not to push toward that goal just to hit a planned stopping point. Vocabulary and the basic picture felt like enough for one session, especially watching how much slower everyone was moving than during, say, the arithmetic operators session a few weeks back. Next time, we solve an actual triangle. Tonight was just learning to see one properly.
Rahul, of everyone, seemed the most at ease with tonight's material despite his usual position slightly behind the group's pace, and I think I understand why: he'd apparently encountered angle measurements before through cricket field markings and bowling run-up distances, in a loose, practical way that gave him a head start on the spatial intuition even without the formal vocabulary. It's a good reminder that "behind" isn't a single, uniform state. He's behind on some things and ahead on others, same as everyone else in that room, myself included on plenty of topics I've never had reason to learn.
Tonight was meant to be a short session: if statements from two weeks ago, plus the else branch for when the condition is False, wrapping up something we'd left half-finished. It stayed short in terms of new material and ran long in terms of laughing, mostly because of Naveen.
I reminded the group that so far, our if statements only ever did something when the condition was True, and did nothing at all otherwise, which is a little unsatisfying if you want the program to respond either way. Else fills that gap: if the condition is False, run this instead. if age >= 10, print you can ride, else print not yet. Straightforward, and everyone got it quickly, probably helped by how much time we'd already spent on plain if statements.
Where it got fun was Naveen deciding to test what happens with an else block that has nothing meaningful in it, printing something absurd like "the universe has ended" for the False case just to see what the program would do. It ran exactly as written, obviously, because that's what code does regardless of how silly the content is, and the group spent a solid few minutes suggesting increasingly ridiculous else messages for each other's programs. Divya's condition checked whether a number was even, and her else branch printed "this number has chosen chaos," which got a genuine laugh out of me too.
I let this run longer than I probably should have, purely because everyone was actively engaged and thinking about conditions and branches, even while being silly about it. There's a difference between off-topic chaos and on-topic chaos, and this felt like the second kind: they were still writing correct if/else logic, just with funnier print statements than I'd have chosen myself.
Rahul, quieter during most of the joking, waited until things settled to ask a genuinely good technical question: whether you could have more than one condition checked in sequence, not just true-or-false but several different possibilities. I told him that's exactly right, and that we'd get there eventually with something called elif, but that tonight was just about the two-option version. He seemed satisfied to have the shape of the bigger idea even without the syntax yet, which is exactly the kind of forward-reaching curiosity I want to encourage rather than brush past because it's ahead of schedule.
I pulled things back with one more serious example: checking a password, printing "access granted" if it matches and "access denied" otherwise, which is a pattern they'll see constantly in any real program. Kiran asked what happens if you get it wrong more than once, whether the program "remembers" previous attempts, which is a genuinely forward-looking question about state and something we haven't covered yet. I told him that's a great question for a few weeks from now, once we've covered loops, and I meant it. I'm writing that one down so I remember to circle back to it directly when we get there.
Tonight ran lighter than most sessions, and I don't think that's a problem worth fixing. Seven weeks after the more grueling stretch through zero-indexing and the beginnings of trigonometry, an easier, funnier session felt earned rather than wasted. I'd rather pace hard topics and easy ones deliberately, in some kind of rhythm, than treat every single week as an equally serious slog. Naveen's chaos era, as I'm now mentally filing tonight, did more for morale than a perfectly efficient lesson plan would have.
Two weeks after introducing sine, cosine, and tangent as ratios, tonight was about actually using them, and I wanted an example that felt like it belonged to the group rather than a textbook scenario imported wholesale from somewhere else. Several of the kids had mentioned flying kites earlier this year, so I built tonight's problem around that instead of the usual ladder-against-a-wall setup I'd normally default to.
Same triangle, different story: string length as the hypotenuse, height as the opposite side.
If you're flying a kite with 50 meters of string fully let out, and the string makes a 50 degree angle with the ground, how high is the kite? The string is the hypotenuse. The height we want is the opposite side, relative to that 50 degree angle. That means we need sine: sin(50°) = opposite / hypotenuse, which rearranges to opposite = hypotenuse × sin(50°).
I let the group work through identifying which side was which before touching any actual calculation, since that mapping, matching the real scenario onto the abstract triangle, is usually the harder half of any applied problem. Sneha got there fastest, correctly identifying the string as the hypotenuse without needing a hint, which tracks with how quickly she's picked up most of trigonometry so far.
Working through 50 × sin(50°), which comes out to roughly 38 meters, felt like a genuinely satisfying payoff after two sessions of vocabulary and setup. Naveen asked if we could check the answer made sense intuitively, since 38 meters out of 50 meters of string feels believably close to fully vertical without being exactly vertical, which is exactly the kind of sanity check I want students reaching for on their own rather than trusting a calculator blindly.
We ran a second version of the same problem with a much shallower angle, 15 degrees instead of 50, and I asked everyone to guess whether the kite would be higher or lower before calculating. Everyone correctly guessed lower, and the actual number, around 13 meters, confirmed it, but Priya pointed out something I hadn't planned to highlight: the string length stayed exactly the same in both versions, only the angle changed, and yet the height changed dramatically. That's a genuinely good observation about which variable is actually doing the work in a formula, and not one I'd explicitly set out to teach tonight.
We closed by brainstorming other situations with the same triangle shape hiding inside them: a cricket ball's trajectory off a bat, the angle of a roof, how far up a wall a ladder reaches. Priya offered the ladder example herself, unprompted, which told me that even though I'd deliberately avoided it as my main example tonight, the underlying idea had generalized well enough that she recognized the shape on her own. That's a better outcome than if I'd just used the ladder example myself and had her memorize that specific case.
I closed the session by asking the group, as a quick check, to identify the hypotenuse in each of the examples they'd just proposed, without doing any actual calculation. Everyone got the roof example right away. The cricket ball trajectory tripped a couple of people up, since it's less obviously a static triangle than a kite string or a ladder, and figuring out where the "sides" even are in a moving, curving path took more discussion than I expected. I let that discomfort sit for a minute rather than resolving it immediately, since noticing that a shape isn't as clean as it first looks is its own useful skill.
Nested conditionals, an if statement inside another if statement, was the topic tonight, and I want to be upfront that it's genuinely a harder idea than most of what we've covered, and I told the group that directly before starting rather than pretending it was just another small step.
I went back to a visual I hadn't used since way back in the if-statement session: nested conditions as a locked door behind another locked door. You need the first key to even reach the second lock, and only then does the second key matter at all. if age >= 13: if has_ticket: print welcome means both conditions have to be True, but they're checked one at a time, in sequence, rather than all at once.
I drew it on screen as literal nested rectangles, an outer box representing the age check, with a smaller box drawn entirely inside it representing the ticket check, and explained that code only reaches the inner box at all if it's already inside the outer one. That visual helped more than the locked-door metaphor alone had, especially once I asked the group what would happen if age was 10: the answer is that the inner check never even runs, because the program never got past the outer door in the first place.
The mechanical difficulty compounded the conceptual one: nested ifs mean nested indentation, and getting the indentation levels right, four spaces for the outer block, eight for the inner one, tripped up nearly everyone at least once. Rahul's first attempt had the inner if statement indented at the same level as the outer one, which meant Python treated them as two separate, unrelated conditions rather than one nested inside the other. We fixed it together, and I made a point of explaining that this exact mistake is common enough that I'd bet money most programmers made it during their own first week with nested logic.
Sneha, working through a separate example checking both age and whether it was a weekend before allowing a discount, asked whether the order of the two nested checks actually mattered, or whether she could put the weekend check on the outside and the age check on the inside instead. I told her it works either way logically, both conditions still have to be true for the inner block to run regardless of which one wraps the other, but that picking whichever condition is cheaper or more likely to fail first, as the outer check, is generally a little more efficient. That's a slightly more advanced idea than I'd planned to introduce tonight, but she'd essentially found her way to it herself, so I followed where the question led.
I didn't push to a second example tonight. Normally I'd want at least two or three variations to reinforce a new idea, but everyone, including the kids who are usually ahead of the pace, seemed genuinely tired by the end of just one. I'd rather end a session with one solid, well-understood example than three rushed ones that blur together by next week. We'll pick this back up with more practice before moving anywhere new.
I want to note, for my own future reference as much as anyone else's, that "the group seems tired" is a real signal worth acting on and not something to push through for the sake of finishing a planned lesson. Six months into this, I trust my read of the room's energy more than I did back in November, and tonight I actually acted on that trust instead of overriding it to hit an arbitrary stopping point. That felt like its own small kind of progress, separate from anything about nested conditionals specifically.
Five months into this, tonight was part review, part something I've been sitting on for a couple weeks and finally got to say out loud: I've booked a trip to Hyderabad, and I'm going to be there in person from May 23rd through June 7th. For part of that stretch, I'll actually be in the same room as everyone I've only ever seen through a laptop camera.
Where these Sunday calls have actually been going, this whole time.
I asked Swathi to keep it as a surprise until I could say it myself on the call, which felt like the right call, and the reaction was worth the wait. Naveen asked, immediately, whether I actually knew how to play cricket or whether that was just something he'd have to teach me once I arrived. Divya wanted to know if I'd be bringing anything. Kiran, characteristically, didn't say much in the moment but asked afterward, through Swathi, exactly which days I'd be there, which I think is his version of being excited: wanting the concrete details rather than the general reaction.
Sneha asked a question I hadn't fully prepared for: whether I was doing this, the trip specifically, because the Zoom classes weren't working. I told her no, firmly, the opposite actually, that the Zoom classes have worked well enough that I wanted more of whatever this is, not a replacement for it. I don't think that fully landed in the moment, and I understand why. From where she's sitting, a teacher deciding to show up in person could easily read as a verdict on everything that came before it. I want to be careful, once I'm actually there, that nothing about the visit implies the last five months were just a placeholder for the real thing.
Before getting into trip logistics, I want to take stock the way I did back in January. Twenty sessions, give or take. A group that's grown from five kids clustered around one screen to a steadier eight or nine most weeks. We've covered most of the Python fundamentals, plus algebra through graphing intuition and trigonometry through basic applications. None of that happened at the pace I originally sketched out in my head back in November, and I've made peace with that. The pace that actually works is the one shaped by a shared laptop, unreliable WiFi, and eight different kids' worth of attention spans, not the one I imagined from my desk beforehand.
I don't want to walk in and treat two weeks in person as a chance to cram material we haven't covered on Zoom. I think the actual value is somewhere else: seeing how these kids interact with each other outside a grid of video tiles, working one-on-one with whoever's been struggling silently in a way a screen makes easy to miss, and honestly, just being an actual person to them rather than a voice with occasional connection lag. I have a rough idea of what I want those two weeks to look like. I expect the reality, like every single session so far, will end up different from the plan in ways I can't predict yet. That's fine. It's mostly been fine so far.
Six more Sunday sessions before I fly out. I want to get through for loops and lists before the trip, so the in-person time can build on a reasonably complete foundation rather than introducing brand new syntax in a noisier, less controlled setting than a quiet Zoom call. We'll see how much of that plan survives contact with reality. So far, none of my plans have survived fully intact, and every single time, what actually happened was better than what I'd originally sketched out.
I also want to use these last six sessions to think harder about what "success" for the trip actually looks like, beyond the obvious answer of everyone having a good time. I keep coming back to something closer to this: I want to leave Kompally in June having actually met these kids, not just having taught in the same room as them for two weeks. Those aren't automatically the same thing, and I think it's worth being deliberate about the difference before I'm there and it's too late to plan for it.
While loops tonight, the first time this group's programs have repeated anything rather than running top to bottom exactly once. I planned a careful, controlled introduction. I did not plan for the best part of the lesson to be an infinite loop crashing the shared laptop's terminal, but that's what actually taught it.
I started properly: a while loop keeps running its indented block as long as its condition stays True, and stops the moment it becomes False. count = 1, then while count <= 5, printing count and increasing it by one each time through, until count reaches 6 and the condition finally fails. Everyone predicted the output correctly before I ran it, five numbers, one through five, which felt like a clean, well-earned win after nested conditionals wore everyone out a couple weeks back.
Naveen asked what would happen if you forgot the line that increases count, and rather than answer, I let him try it himself. The condition never became False, because nothing was ever changing, and the loop ran forever, printing 1 over and over until the terminal itself started struggling to keep up with the output and the whole shared laptop noticeably slowed down. We had to force the program to stop entirely.
That was, genuinely, one of the more useful five minutes of the entire series so far. Nobody needed me to explain why an infinite loop is dangerous after watching one physically bog down the machine in front of them. Sneha asked, slightly alarmed, whether we'd broken the laptop, and I got to explain that no, the program was just doing exactly what it was told, forever, and stopping it manually was a completely normal and expected part of working with loops, not an emergency.
Once the laptop recovered, I asked the group why the loop hadn't just stopped on its own eventually, expecting someone to say it was waiting for count to somehow reach 6. Divya got there first: nothing was making count change anymore, so there was nothing that could ever make the condition False, no matter how long we waited. That's exactly the right mental model, and I was glad it came from her rather than from me repeating the explanation a third time.
Once things calmed down, I had everyone write their own while loop, deliberately, then trace through it line by line on paper (or the closest phone-notes equivalent) before running it, specifically checking whether the condition would ever actually become False. This is a habit I want to build early, since infinite loops are one of the most common bugs any new programmer writes, in any country, on any first attempt at loops. Better to be a little paranoid about it now than to keep crashing programs by accident for months.
Naveen, for the rest of the session, kept referring to the accidental infinite loop as "the machine that broke," slightly proud of having caused it. I told him that's a very normal rite of passage, and that I've done the exact same thing myself, more than once, at every stage of learning to code, including well past when I should have known better.
Before we signed off, I made a point of writing "the machine that broke" on my own notes as an actual phrase I want to reuse deliberately the next time I introduce loops to a brand-new group, whether that's here or back with the KidCode bootcamp at home. Naveen accidentally invented a better teaching tool than the one I'd planned to bring, and I don't see much point in pretending otherwise just because it wasn't my idea originally.
Statistics started tonight, and I wanted a data set that felt real rather than abstract, so I asked the group beforehand, through Swathi, to bring five numbers each representing something they actually cared about counting. Most of them came back with cricket scores.
Using one set of five match scores, 24, 31, 18, 65, and 27 runs, we calculated all three measures together. The mean, adding everything and dividing by five, came out around 33. The median, the middle value once sorted, was 27. The mode would need a repeated value to exist at all, and this particular data set didn't have one, which turned into its own useful point: not every data set has a mode, and that's fine, it's not a sign anything went wrong.
Sneha noticed, without me pointing it out, that the mean and median gave pretty different answers, 33 versus 27, and asked why they weren't the same number if they're both supposed to describe "the middle" in some sense. That's a genuinely sharp observation for a first exposure to the topic.
I explained that the 65-run match was pulling the mean upward, since the mean is sensitive to unusually large or small values in a way the median isn't. The median only cares about position once sorted, not magnitude, so one huge outlier barely moves it. This is the kind of thing that's much easier to feel with a concrete, personally chosen data set than with numbers pulled from a textbook, and I think using their own cricket scores instead of generic examples paid off exactly here.
I asked the group to imagine a sixth match added to the data set, a genuinely disastrous one, say a score of 2, and predict what would happen to the mean versus the median before actually recalculating. Most guessed correctly that the mean would drop noticeably while the median would barely move, which told me the outlier sensitivity idea had actually taken hold rather than just being nodded along to. Rahul asked, reasonably, whether real cricket statisticians actually worry about this kind of thing, and I told him yes, constantly, which seemed to raise the whole topic's credibility in his eyes more than anything I'd said about the math itself.
Naveen asked which of the three is the correct one to use, expecting, I think, a single clean answer. I told him honestly that it depends on what you're trying to describe, and that this is actually one of the more common ways statistics gets used to mislead people: picking whichever measure tells the story you want told, rather than the one that best represents the actual data. That felt like a slightly heavier note to end a first statistics session on, but I think it's an important one, and better to plant it early than treat these three numbers as interchangeable synonyms for "the answer."
Divya asked, near the very end, whether advertisements do this on purpose, picking the flattering number rather than the honest one. I told her, carefully, that sometimes yes, and that being able to ask "which measure are they actually using here, and why that one" is a genuinely useful skill for reading the world outside of any classroom. That felt like a bigger takeaway than mean, median, or mode individually, and probably the one I most want this group to still be carrying years from now, long after they've forgotten which one is calculated by sorting the list.
Following straight off while loops from a couple weeks back, tonight was for loops, specifically for repeating something a known number of times rather than until some condition changes. It's a small addition on top of what they already know, and it produced one of the cleanest, most satisfying breakthroughs I've seen in this series so far.
I framed the distinction directly: use a while loop when you're repeating until something changes, and a for loop when you already know exactly how many times you want to repeat. for i in range(5) runs the indented block five times, with i taking the values 0 through 4 along the way. Divya asked immediately why it starts at 0 and stops before 5 rather than going 1 through 5, and I got to reuse the zero-indexing explanation from the strings session back in January, which she remembered clearly enough to nearly answer her own question before I finished.
The moment that actually landed hardest was comparing a for loop against the alternative: what if you wanted to print numbers one through ten without a loop at all, just ten separate print statements typed out by hand. I had everyone actually type all ten manually first, tediously, before showing the three-line loop that does the exact same thing. Watching Priya's face when she realized the loop replaced ten lines she'd just spent two minutes typing was genuinely one of my favorite moments of the whole series. She said, more or less, "wait, that's it?" in a tone somewhere between delighted and mildly betrayed that she'd typed all ten by hand for nothing.
I gave everyone a stretch goal: print only the even numbers from the loop instead of every number, which requires combining a for loop with the modulo operator from back in February. Sneha got there first, using if i % 2 == 0 inside the loop, and explained her own reasoning to the group before I could ask her to. Naveen took a different, slightly messier approach, looping through odd numbers of range differently, and it also worked, which was a good chance to point out that there's rarely exactly one correct way to solve a problem in code, just different ones with different tradeoffs.
I had the two of them, Sneha and Naveen, explain their competing approaches to each other directly rather than through me, mostly out of curiosity about how that conversation would go. It went well. Sneha walked through her modulo check clearly, and Naveen, to his credit, admitted his version was "probably more confusing" once he heard hers laid out cleanly, without any defensiveness about it. I want more of that exact dynamic in this group: comparing approaches honestly rather than everyone quietly assuming their own first idea was the best one.
This felt like a strong place to be sitting, curriculum-wise, with the trip two weeks out. For loops, combined with everything before them, gives the group a genuinely solid toolkit heading into lists next week, which I want to get through before I fly out so the in-person sessions can build on something complete rather than half-finished.
I ended tonight thinking less about the curriculum and more about how different this group looks compared to five kids clustered around one small screen back in November. Eight, sometimes nine kids now, several of them explaining concepts to each other unprompted, one of them apparently self-teaching between sessions out of genuine curiosity. None of that shows up on a syllabus. All of it feels more important than the syllabus, most weeks.
Probability tonight, continuing the statistics thread from a couple weeks ago, and I built the session around something everyone could physically do rather than just calculate: flipping an actual coin, twenty times, and comparing what we predicted against what actually happened.
Before flipping anything, I asked the group what they expected: out of twenty flips, how many heads. Every single answer was some version of "ten," which is the mathematically expected value, heads coming up roughly half the time. I confirmed that's the right intuition, a fair coin should land heads about half the time over many flips, and then we actually did it, with Rahul flipping a coin on his end of the call, twenty times, calling out each result.
We got twelve heads and eight tails, not the ten-ten split everyone had predicted, and I watched a couple of the kids look almost suspicious of the coin, like something must have gone wrong. Nothing went wrong. That's the actual point of the lesson.
I explained that "half the time" describes what happens on average, over many, many flips, not a guarantee for any specific twenty. Small samples wobble around the expected value, sometimes a lot, and that's normal rather than evidence the coin is unfair. Sneha asked how many flips it would actually take to get close to a real fifty-fifty split, which is a great question that goes well beyond what I'd planned to cover, and I told her honestly that it takes a lot more than twenty, often hundreds, before the wobble smooths out. She seemed a little disappointed that math didn't give a cleaner, faster answer, which I understand completely. I was disappointed by the same thing at her age too.
We closed with a quicker example using a six-sided die: the probability of rolling any specific number is one out of six, and the probability of rolling an even number is three out of six, or one half, since three of the six faces qualify. Naveen connected this back to the modulo work from February almost immediately, noting that checking "even" here is the same idea as checking number % 2 == 0 in code, which is exactly the kind of cross-topic connection I love seeing show up unprompted.
Priya asked whether we could roll a die a hundred times, just to see if the results actually smoothed out the way I'd claimed they would with more trials. We didn't have time to do it live, but I promised we'd revisit the idea properly once I'm back from the trip, maybe as a small group project where everyone rolls dice at home between sessions and we pool the results together. I like that this idea came from genuine skepticism about something I said rather than blind acceptance of it, and I want to reward that instinct by actually following through on it.
One more session next Sunday before the trip. I want to squeeze in lists, even briefly, and then spend most of that session just talking through what the next two and a half weeks are actually going to look like, for them as much as for me.
I keep noticing, writing these posts, how much the "hard" sessions and the "easy" sessions have started blending together into something steadier than either extreme. Tonight wasn't a breakthrough and it wasn't a struggle. It was just a normal, competent class, the kind that doesn't generate a dramatic story but quietly represents six months of things going right more often than they go wrong. I don't want to undervalue that just because it doesn't make for the most exciting post in the series.
Last Zoom session before the trip. Six and a half months since that first shaky call in November, and tonight felt like it wanted to be both a normal class and a send-off at the same time, which is an odd needle to thread in one hour.
I introduced lists quickly, more as a preview than a full lesson: a way to store many values under one name, like scores = [24, 31, 18, 65, 27] instead of five separate variables. We connected it immediately back to the cricket-scores statistics session from a few weeks ago, since that data set is exactly the kind of thing a list is built to hold. I didn't go deep tonight. I want the real, hands-on list lesson to happen in person, on a whiteboard, where I can walk through indexing and looping over a list more slowly than a rushed pre-trip session allows.
I told the group what I'm actually bringing, partly because they asked and partly because saying it out loud made the trip feel more real to me too: a small set of whiteboard markers, since I don't know what's available on-site, some printed backup exercises in case the venue's internet is worse than what we've dealt with on the shared laptop, and not much else beyond the ordinary stuff you'd pack for two and a half weeks away.
I told them, honestly, that I'm nervous, in a good way, about meeting everyone in person after months of knowing them only as small rectangles on a screen with occasional lag. Divya asked if I was scared. I said not scared, just aware that in-person teaching is going to be different in ways I can partly predict and mostly can't, and that I'd rather admit that upfront than pretend I've got it all figured out.
Swathi asked, separately from the kids, whether I wanted a structured itinerary waiting for me or whether I'd rather figure out the shape of each day once I arrived. I told her I genuinely didn't know yet, and we agreed on something loose: a rough plan for the first day, nothing beyond that, adjusted daily based on what's actually working. Six and a half months of this project bending around reality rather than my original intentions has taught me not to over-plan something I have this little visibility into yet.
Naveen asked if I was worried they'd be different in person than I'd imagined, or that I'd be different than they'd imagined. Fair question, and one I didn't have a rehearsed answer for. I told him honestly that six months of a laptop camera has probably given me an incomplete picture either way, and that I was looking forward to finding out what I'd been missing rather than dreading it. He seemed satisfied with that, or at least satisfied enough to move on to asking, again, about the cricket bat.
Looking back at the full arc tonight, rather than just the last few weeks, put something in perspective: print statements and installing Python in November feel like a different lifetime from nested conditionals, trigonometry applications, and probability in May. Not because any individual week was dramatic, but because they added up, slowly, the way most real progress does. I don't think anyone in that Zoom call, myself included, fully registers how far this has come until a moment like tonight forces a look backward.
Next post: from Hyderabad, in person, for the first time. See you on the other side of a plane ride.
I landed in Hyderabad two days ago, mostly spent recovering from the flight and adjusting to the time change, and today was the first day I actually walked into the children's home in Kompally rather than logging into it. I want to write this one carefully, because I think it's the post in this whole series I'll come back and reread the most.
Swathi met me at the gate, and I recognized her instantly despite having only ever seen her step briefly into frame to translate something over the last six months. The building itself is smaller than I'd pictured, a two-story home with a courtyard that does double duty as a play area and, apparently, today, a classroom. There is one real classroom-ish room with a chalkboard, and a shelf with maybe a dozen books, and that's more or less the extent of the dedicated space. I'd known, intellectually, that resources were limited. Seeing it in person is a different kind of knowing.
I heard them before I saw them. A group of kids, more than I expected in the moment, came around the corner of the courtyard at something close to a run, and I picked out faces I recognized from six months of laptop screens before anyone said a single name. Divya reached me first, talking fast, asking questions faster than I could answer them. Kiran hung back slightly, the way he does, but he was smiling in a way I don't think a webcam has ever fully captured. Naveen, predictably, immediately asked if I'd brought a cricket bat.
I want to record the specific, small details of that first minute before they blur together in my memory the way first impressions eventually do. Divya is shorter in person than the camera angle had suggested, and talks with her hands constantly in a way six months of a fixed webcam frame had completely hidden from me. Sneha was wearing a cricket team jersey, not a coincidence I'd assumed, apparently a genuine favorite. Naveen has a scar on his eyebrow he never once mentioned or that I never once asked about, and I found myself wondering, standing there, how much else about each of these kids I simply hadn't had the resolution to notice through a laptop camera and an unreliable connection.
The actual chalkboard in the actual room. No screen share, no frozen frame, no lag.
There were a few kids in the group I didn't recognize at all, which caught me off guard for a second before I remembered: not every kid at the home has consistent access to a phone or a turn on the shared laptop during our Sunday calls, even if they're around and interested. Swathi introduced a few of them, including a younger girl named Lakshmi, maybe eight or nine, who I hadn't taught even once and who was, somehow, the most immediately talkative of the entire group. I don't know yet whether she'll want to join the regular sessions going forward, in person or on Zoom, but I made a point of including her in everything today regardless.
Nine kids in one physical room is a completely different experience from nine faces in a video grid, in ways I anticipated and ways I didn't. The things I expected: side conversations happening constantly, kids physically getting up and moving around in a way a Zoom call structurally prevents, needing to actually raise my voice to be heard over general noise. The thing I didn't fully expect: how much easier it was to read the room. On a screen, I've spent six months guessing whether someone's confused from a slight pause or a strange facial expression pixelated at low bandwidth. In person, I could see it instantly, on more than one face, the exact moment something didn't land.
I didn't teach anything structured today, on purpose. We played a couple of quick logic games, similar to the riddle session back in December, but mostly today was just being in the same space: names, faces, ages, a few kids showing me around the courtyard, Naveen genuinely trying to teach me proper cricket grip for twenty minutes with a bat that's seen better days. I let today be that, entirely, rather than rushing toward the whiteboard lesson I'd half-planned on the flight over.
At one point, sitting on a low wall at the edge of the courtyard, Kiran sat down next to me without saying anything for a while, which is exactly the kind of quiet I've learned over six months isn't discomfort, just his normal register. Eventually he asked, in careful, slightly formal English, whether I found teaching him and the others "hard from far away." I told him yes, sometimes, and that I thought about all of them a lot more during the week than a one-hour Sunday call would suggest. He nodded, seemingly satisfied, and went back to watching Naveen fail to teach me anything useful about batting technique. I don't think that exchange lasted even a minute. I don't think I'll forget it either.
Swathi mentioned, at one point during the afternoon, that a few kids had asked her more than once over the past week exactly how many days until I arrived, apparently counting down the way you'd count down to a birthday. I hadn't fully processed, until she said that, that the anticipation had been running in both directions this whole time. I'd been nervous about meeting them. It hadn't quite occurred to me, as clearly as it should have, that they might have been just as keyed up waiting for me.
A few things are already clear that six months of Zoom couldn't have told me. The group is bigger in person than the regular Sunday attendance suggested, and I need to think about how to include kids like Lakshmi without derailing the pace for kids who are seven months into the curriculum already. The physical space is smaller and less resourced than I'd pictured, which changes what kind of activities are actually practical. And somehow, despite months of lag and frozen screens and a shared laptop that's given us nothing but trouble, these are exactly the kids I thought they were. Meeting them in person didn't change who I thought they were. It just made it real in a way a screen, no matter how good the connection, never quite manages.
First real lesson in person today: functions, which we hadn't gotten to on Zoom before I left. I quickly discovered that teaching code on a physical chalkboard is a meaningfully different skill from teaching it over a screen share, and not one I'd actually practiced before today.
On Zoom, if I mistype something or want to restructure an example halfway through, I delete it and retype it, instantly, without anyone really registering the interruption. On a chalkboard, with a handful of chalk pieces and one dry rag serving as an eraser, every mistake is a small physical production: erasing, re-explaining what changed and why, sometimes smudging half a line I didn't mean to touch. I wrote a function definition with a genuine indentation mistake, chalk-drawn spacing being a lot less precise than a text editor, and had to physically erase and redraw the whole block rather than just fixing one line.
Reconstructed here more neatly than it looked on the actual board by the end of the lesson.
Oddly, I think this made the lesson better, not worse. Watching a mistake happen and get physically corrected, in real time, in a way that costs visible effort, seemed to land harder than a clean, instant Zoom correction ever has. Rahul pointed out the indentation error himself before I'd finished redrawing it, which is exactly the kind of active error-spotting I've been trying to encourage for months and rarely see happen this fast.
I also noticed, mid-lesson, that I was writing more slowly and deliberately on the chalkboard than I ever type on a screen, and that the slower pace was inadvertently giving the group more time to absorb each line before the next one appeared. That's not something I would have predicted walking in this morning. The constraint I was mentally filing as a pure downside turned out to have a real, if accidental, upside baked into it.
I introduced functions as a way to package up instructions you want to reuse, rather than retyping the same lines over and over, building directly on the ten-print-statements moment from a few weeks ago with for loops. def greet(name), followed by a print statement, then calling greet("Kiran") and greet("Divya"), reusing the exact same instructions with different input each time. Naveen, predictably, wanted a cricket example, so we rewrote it as a function that takes a player's name and prints their batting statistics, which he then insisted on running for every player he could think of.
Beyond the chalkboard, today made the resource constraint more concrete than any Zoom session had: there's one laptop capable of actually running Python at the home, and nine kids wanting hands-on time. We worked in small groups of two or three, rotating through the laptop while everyone else worked through the function examples on paper, tracing through what the code should print by hand before ever seeing it actually run. It's slower than everyone having their own machine, obviously, but it forced a kind of careful, deliberate tracing-through-code-by-hand that I don't usually get on Zoom, where it's too easy to just run something and see what happens instead of predicting first.
I'm going to prep chalkboard examples more carefully in advance, maybe even sketch them out on paper first so I'm not improvising indentation live and risking exactly today's mistake. And I want to lean into the paper-tracing workaround rather than treating it as a compromise, since it might genuinely be a better way to build the habit of reading code carefully before running it, one I could bring back to Zoom sessions even once the laptop bottleneck isn't forcing the issue.
Naveen's cricket-stats function ended up staying up on the board for the rest of the afternoon, well after the lesson officially ended, with kids drifting back over to add more players to it between other activities. I hadn't planned for a whiteboard example to have an afterlife once class was technically over. I like that it did.
Today I spent a good chunk of the session working one-on-one with Kiran, away from the rest of the group, who were deep into a paper-tracing exercise with Swathi keeping an eye on them. I've written about Kiran's quietness a handful of times over the last six months, always guessing at the reason without ever quite asking directly. Today I asked directly.
I sat with him near the courtyard, going through a nested-conditional problem he'd been stuck on, and at some point I just asked, plainly, whether the group calls felt harder for him than they seemed to for everyone else. He said yes, and then, after a pause that felt different from his usual quiet, he said he didn't like being wrong in front of everyone on the call, especially with Naveen and Divya, who answer fast and rarely seem to doubt themselves.
That reframed six months of my own assumptions in about ten seconds. I'd wondered about language barriers, shyness as a personality trait, disengagement. It was mostly none of those, or at least not the main thing. He's been thinking just as fast and just as correctly as anyone else in that group, based on everything I've seen him produce when he does speak, but the group setting itself, specifically the risk of being visibly wrong in front of peers, was the actual obstacle.
One-on-one, with nobody else listening, he worked through the nested conditional faster than I expected, talking through his reasoning out loud the entire time, correcting himself mid-sentence without any visible discomfort about it. It was, genuinely, some of the clearest step-by-step reasoning I've heard from anyone in this group, delivered by the kid I'd most consistently underestimated in terms of verbal participation.
I asked him, once we'd finished the actual problem, whether he'd have preferred all along if these sessions had more one-on-one time built in, even if that meant less time as a full group. He thought about it for a while before answering, which is very him, and said he liked the group too, he just needed the quieter version sometimes to actually say what he was thinking rather than just think it. That's a distinction worth holding onto: not choosing between group and individual time, but recognizing that some kids genuinely need both, in some rotation, to fully show up.
I don't have a clean fix for group dynamics on a Zoom call, but I do have an idea worth trying once we're back online: cold-calling less, and building in more structured pair work, even over video, where kids answer to one other student before answering to the whole group. Lower stakes, more thinking room, less performance pressure. I wish I'd tried this back in December instead of assuming quiet meant something else. I'm glad I found out now rather than not at all, but I'd be lying if I said I didn't feel a little bad about six months of possibly misreading him.
Before we rejoined the group, Kiran asked if he could be the one to explain the nested conditional to everyone else once we regrouped, rather than me explaining it again. I said of course, and he did, clearly and correctly, in front of the whole group, for maybe the first time since November. Nobody made a big deal of it. I did, quietly, to myself, walking back to where I'm staying tonight.
I don't want to overstate one good afternoon into some kind of permanent transformation. Kiran might be quiet again next Sunday, back on Zoom, back in the full group, and that would be fine too. But I know something true about him now that I didn't know yesterday, and I plan to keep making room for the version of him I saw today, on purpose, rather than waiting for it to show up on its own again.
Today was the first fully collaborative group activity of the trip, and I built it around cricket because at this point I'd have to be actively avoiding the sport to keep missing how much this group cares about it. We split into three small teams, each tracking made-up match scores across a few rounds of a simplified game we played in the courtyard using a tennis ball and a real bat, then used those actual numbers for a statistics exercise afterward.
We played a short, informal match, three teams rotating batting and bowling, and Lakshmi, the younger girl I'd met on day one, turned out to be a genuinely sharp bowler, which none of the older kids had apparently anticipated and which she seemed quietly delighted by. I kept score on paper as we went, team totals across a few rounds, specifically because I wanted real numbers the group had just personally generated, not numbers handed down from a textbook or even from a previous Zoom session.
Back at the whiteboard, we calculated mean, median, and mode for the round-by-round scores, and this time, unlike the Zoom version of this lesson back in April, the group actually argued about which measure best represented "how good" each team really was. Naveen's team had one huge outlier round that pulled their mean up significantly higher than their median, and he wanted to use the mean to argue his team was the best. Divya, on a different team, pointed out, correctly, that one lucky round doesn't make a team consistently good, and argued the median was the fairer measure.
I mostly stayed out of it and let them argue it through, because they were doing, unprompted, almost exactly the reasoning I'd tried to plant back in the original Zoom lesson about picking a statistic to tell the story you want told. Watching them independently arrive at genuine skepticism about which number "proves" what, arguing about it with real stakes, however small, felt like a bigger win than any explanation I could have given at a whiteboard.
Lakshmi, playing in her first proper match today, ended up being the deciding factor in the argument without meaning to: her one outstanding bowling round was exactly the kind of outlier the group was debating about, real and dramatic enough that nobody could dismiss it as a fluke once they'd watched it happen live. It's one thing to discuss an outlier as an abstract number on a bar chart. It's another to watch the actual kid who produced it standing right there, grinning, while her teammates argue about whether she should count.
This kind of activity, physical, noisy, three small teams working semi-independently while I circulated between them, just isn't something a single shared laptop and one video call can support. I'm going to think hard about which parts of today are worth trying to approximate once we're back to Zoom, and which parts genuinely only work because everyone's in the same courtyard, with a real ball and a real bat and real disagreement about whose team actually won.
I think the closest I can get on Zoom is something like this: assign teams ahead of time, have each team track a small set of real numbers from their own week, anything, steps walked, hours read, doesn't have to be cricket, and bring them to the following session for exactly this kind of comparison. It won't have a tennis ball or a courtyard, but the core of what worked today, real numbers the kids generated themselves and genuinely cared about defending, might transfer more than I initially assumed.
I want to write this one honestly rather than smoothing it over, because today was genuinely the hardest teaching day of the whole trip, and I think the difficulty itself is worth documenting as much as any breakthrough.
Lakshmi and two of the other younger kids who've joined since I arrived are noticeably more comfortable in Telugu than in English, more so than the regular Zoom group, who've had six months of English-heavy technical vocabulary to adjust to. Today's lesson, on lists, needed real-time translation for a meaningful chunk of the group, with Swathi and, at times, Divya stepping in to help bridge specific phrases. That slows everything down in a way that's hard to plan around precisely, since I never know in the moment exactly which word is going to need translating until it visibly doesn't land.
Partway through, with the whiteboard half-covered in a list example, the power cut out entirely, which apparently happens periodically and isn't treated as an emergency by anyone but me. No lights, no fan in what was already a warm room, and a whiteboard that was suddenly much harder to see clearly. I didn't have a backup plan for this specific combination of problems, so I improvised: we moved the group outside into the courtyard, where there was still daylight, and I switched from the whiteboard to just talking through lists conceptually, using physical objects, actual pebbles arranged in a row, as a stand-in for list indexing.
I won't pretend this was a smooth improvisation. The pebble-list explanation was rougher than the whiteboard version I'd planned, and I lost the thread once or twice trying to translate my own explanation into simpler English for the kids who needed it, then losing a beat trying to remember where I'd left off with the kids who didn't. Rahul, who's normally solidly in the middle of the group's pace, seemed genuinely lost by the end, and I don't think today's version of the lesson served him as well as a calmer session would have.
I checked in with him specifically before we wrapped up for the day, and he admitted, honestly, that he'd stopped following about halfway through and had just been nodding along, hoping it would make sense later. I appreciated the honesty more than I was frustrated by the gap, and told him so directly. I'd rather a student tell me they're lost, even after the fact, than perform understanding they don't have. We agreed to go over lists again together tomorrow morning, before the rest of the group's session, just the two of us, power permitting.
I don't think every post in this series needs a clean lesson learned at the end, and I don't want to force one here. Today was hard because of two things stacking on top of each other that I don't fully control: a real language gap I'm still learning to navigate, and infrastructure that isn't as reliable as what I'm used to. Both of those are just true about teaching here, not problems with a tidy fix. What I can control is planning tomorrow's session with more slack built in, and maybe asking Divya, gently, whether she'd be willing to help translate key vocabulary ahead of time rather than only in the moment. I'll try that next.
After yesterday's rough session, I wanted something lower-pressure today: a review, not a test in any formal sense, just a chance to see what's actually stuck after seven months of Sunday Zoom calls plus a week and a half of chalkboard chaos.
We sat in a rough circle in the courtyard, and I went around asking each kid to explain one concept, in their own words, to the group. Not the textbook definition, just however they'd explain it to a friend who'd never coded before. Divya took variables, describing them almost exactly the way I taught them back in November, labeled boxes, which either means the explanation stuck word for word or she's genuinely internalized the idea deeply enough to reconstruct it independently. Either way, I'll take it.
Naveen got assigned functions, and rather than the greet-and-cricket-stats example we'd actually used, he explained a function as "a box that eats an argument and something happens, and sometimes it yells back at you," which is, charitably, about sixty percent accurate and delivered with such total confidence that the whole group, myself included, lost it laughing before I could even attempt a correction. I let the laugh run its course before gently fixing "yells back at you" into "can optionally give you something back," which, once he stopped laughing at his own answer, he actually understood better than I expected.
Sneha got booleans and comparisons, the oldest material in the whole stretch, and gave what I'd call the single cleanest explanation of the entire review: "it's just a question the code can only answer with yes or no, nothing in between." I've used more words than that to explain the same idea more than once this year. Sometimes the shortest version turns out to be the correct one.
Not everything was a clean win. Rahul, still working through yesterday's rougher lesson, couldn't cleanly trace through a for loop by hand without running it first, which tells me that specific muscle, predicting before executing, hasn't fully formed for him yet even though it has for most of the group. That's useful, concrete information, more useful than a vague sense that "Rahul's a bit behind," which is closer to what I'd have said before today's exercise gave me something specific to actually work on with him.
I spent a few extra minutes with him after the group activity wound down, walking through a simple loop line by line on paper, covering the output and asking him to predict it before uncovering it. He got there, slower than most of the group but correctly, and I told him that speed isn't really what I'm grading for, understanding is, and he clearly has the second one even if the first one takes him a little longer than it takes Divya or Sneha.
I want to bring this exact activity back to Zoom sessions going forward, even without a courtyard and a circle of chairs. Having a student explain a concept in their own words, to the group, surfaces both confidence and gaps far better than my asking "does this make sense" ever has, and it's low-stakes enough, especially with the peer-explanation habit Kiran started a few weeks back, that I think even the quieter kids would engage with it more than a direct question from me.
Tomorrow is the last full day before I fly home. I don't think I'm ready for it, and I don't think that's really something you can prepare for regardless of how many days of notice you give yourself.
Fifteen days ago I walked into the children's home in Kompally not knowing exactly what to expect. Today I walked out of it for the last time this trip, and it was harder than I'd let myself admit it would be beforehand.
I wanted the last real lesson to be something that felt like a finished, whole thing, not another isolated concept. We built a number guessing game together: the computer picks a random number between 1 and 20, the player guesses, and the program tells them whether they got it right. It pulls together input, conditionals, and a new piece, Python's random module, into one small, complete program that actually feels like a real game rather than a syntax exercise.
I had each kid extend it themselves afterward, in whatever direction they wanted. Sneha added a loop so you could keep guessing until you got it right, using the while loop from back in April without needing a hint from me. Naveen, unsurprisingly, turned his into a cricket-themed guessing game, guessing a bowler's speed instead of a random number, complete with his own trash talk printed for wrong guesses. Divya added hints, telling the player whether their guess was too high or too low, which is a genuinely clever extension I hadn't specifically taught and was glad to see her reach for on her own.
Rahul's version, quieter than the others, might be the one I'm proudest of. He kept it simple, no hints, no loop, exactly the base version, and instead spent his extra time making sure he could explain every single line of it back to me without looking at the screen. Given where he started, missing the first two months of the curriculum entirely and spending most of the year slightly behind the group's pace, a correct, fully understood, unglamorous program is a bigger achievement than a flashier one built by someone who'd had a head start. I told him that directly, and I meant every word of it.
Priya and Lakshmi worked together on a shared version, trading the keyboard back and forth, which nobody had assigned and which I noticed only once it was already well underway. Watching a kid who'd been at her first-ever session two weeks ago collaborating naturally with someone who'd been here since November, neither one visibly aware of the gap between them, might be the single image from this whole trip I keep coming back to.
Near the end, I just watched for a few minutes rather than teaching anything. Sneha helping Rahul debug his loop. Kiran explaining his own version to Lakshmi, patiently, the way I'd have explained it to him back in December. Naveen loudly guessing wrong on purpose, apparently, just to hear his own trash-talk messages print again. Seven months of Sunday calls and two and a half weeks of chalkboard chaos, distilled into one afternoon where I genuinely wasn't the most necessary person in the room for a while, and that felt like exactly the right note to end this stretch on.
I told the group, plainly, that this wasn't really a goodbye, just a return to Sunday Zoom calls, the way we'd been doing it before I ever booked a flight. I mostly believe that myself. It still felt heavier than I expected saying it out loud in person, watching Priya's face fall slightly despite my best reassurance, and Lakshmi, who I've genuinely only known for two weeks, asking if I was definitely coming back sometime, not just saying it to be nice.
I told her yes. I meant it when I said it, and I mean it more now, writing this a few hours later with the noise of the courtyard still somehow in my ears. Swathi walked me to the gate, the same gate I'd walked through nervous and unsure fifteen days ago, and said the kids have talked about almost nothing else since I told them about the trip back in April. I don't entirely know what to do with a sentence like that except try to be worth it.
Naveen, who'd spent two and a half weeks turning every possible topic into a cricket reference, gave me an actual, real cricket ball as I was leaving, slightly scuffed, clearly one he'd owned and used rather than something bought new for the occasion. I didn't expect that and didn't entirely know how to respond in the moment beyond thanking him, probably too many times. Divya gave me a folded piece of paper I didn't open until I was back at where I'm staying: a list, in her handwriting, of every topic we'd covered since November, from memory, with small notes next to a few of them. Next to "if/else" she'd written "this one took me the longest but I like it best now." I don't think I need to explain why that's the piece of paper I'm keeping.
I don't think the in-person visit changed the curriculum trajectory much, honestly. We're roughly where I'd have expected regardless. What it changed is harder to put a number on: I know what Kiran's actual laugh sounds like now, not the slightly delayed version a laptop speaker gives you. I know Divya explains things to other people the exact same patient way I try to explain things to her. I know Naveen will find a cricket angle on literally any concept I teach him, forever, and I've decided that's a feature of this group, not something to redirect. Seven months of Sunday calls built the foundation. Fifteen days in Kompally made all of it real in a way I don't think a screen, no matter how reliable the connection, was ever going to manage on its own.
Next Sunday, we're back on Zoom. I'll write about how that feels once I know myself.
First Zoom call since getting back from Hyderabad, a week after saying goodbye at the gate of the children's home. I expected it to feel like a step backward, in-person teaching being obviously richer, and in some ways it did. In other ways, it felt better than I'd braced for.
Eight tiles tonight, counting Lakshmi and Vamsi, who've now joined the regular Sunday group.
The obvious thing: I could actually picture the room behind each small video tile now, in a way I genuinely couldn't back in November. When Naveen answered tonight, I wasn't picturing an anonymous kid at a laptop, I was picturing him specifically, in the courtyard, badly explaining what a function does and making the whole group laugh. That mental image didn't exist eight months ago, and it changes how I listen, even over a laggy connection.
The technology hasn't changed at all, for the record. Same shared laptop, same occasionally unstable WiFi, same slightly delayed audio that makes people accidentally talk over each other. I'd half-expected two weeks of in-person familiarity to make the technical friction feel worse by comparison, more of a letdown. It didn't, really. If anything, knowing the actual room and the actual kids behind the screen made the technical friction feel like a minor annoyance rather than the whole texture of the class, which is closer to how it should have felt all along.
Lakshmi and Vamsi, both of whom I only met in person, joined tonight's Zoom call for the first time, which means the group has grown again, now eight kids fairly regularly rather than six or seven. Lakshmi, exactly as talkative on a screen as she was in the courtyard, answered questions before I'd finished asking them, which tracks with everything I saw from her in Kompally. Vamsi, an older kid I hadn't written much about yet, mostly listened tonight, more Kiran-circa-November than anything else, and I'm going to apply what I learned from that specific mistake and not assume quiet means confused this time around.
Kiran, notably, spoke up twice tonight without me prompting him, which I'm choosing to read as evidence that whatever shifted during our one-on-one conversation in Kompally wasn't just a product of being physically in the same room. It carried back across the screen with him, at least a little. I don't want to read too much into two unprompted comments in one session, but I noticed, and I wrote it down, and I'll keep watching for whether it holds.
Curriculum-wise, we're returning to where I'd more or less left off before the trip, lists and the beginning of functions, both of which got a rougher, chalkboard-and-pebbles treatment in Kompally that I want to formalize properly now that everyone's back on a shared screen with actual code running rather than gestures at pebbles. I didn't push far into new material tonight. Mostly this was about re-establishing the rhythm, checking in on how everyone's doing since the trip ended, and, if I'm honest, checking in on how I'm doing with it too.
I thought coming back to Zoom would feel like a loss. Instead it feels more like the class has two different textures now, both real: the screen-and-lag version that's sustainable every single week, and the in-person version that isn't sustainable but is, when it happens, something else entirely. I don't think I need to pick one as the "real" version of this class. They're both real. I'm just glad I get both.
I ended the call tonight the same way I've ended most calls this year, asking if anyone had questions before we signed off, and for the first time in a while, nobody did, not because the material was too easy to question, but because everyone seemed simply glad to be back in the rhythm of a normal Sunday. I'll take that as its own quiet answer to a question I'd been carrying since the flight home: whether this would still feel like the same class once the in-person chapter ended. So far, it does. Different, but the same underneath.
Eight months in, I'm noticing the single-concept-per-session format that carried us through the fall and winter is starting to feel a little small for where this group actually is. Tonight was the first session I planned explicitly as a small project rather than one new idea, building a basic calculator that takes two numbers and an operator and prints the result.
Deliberately, there's no new syntax in this project. Input, float conversion, if/elif/else branching on the operator, all things covered months ago. The point wasn't to learn something new, it was to practice assembling familiar pieces into something a little more substantial than a five-line example, which is its own skill, separate from learning any individual concept.
Sneha finished the core version fastest, then asked on her own whether she could add error handling for dividing by zero, a case we hadn't discussed at all. I let her puzzle through it rather than immediately explaining, and she landed on checking whether the second number equals zero before doing the division, printing a message instead of letting the program crash. That's a genuinely solid instinct, defensive thinking about what could go wrong, that I hadn't explicitly taught and was glad to see emerge from her own curiosity.
Handling four different operators with the existing if/else structure meant nesting or chaining multiple conditions, which was the natural moment to introduce elif, a shorthand for "else, if this other condition," rather than nesting an if inside every else block. I hadn't planned to teach elif tonight specifically, but the calculator practically demanded it once we got past the second operator, and I think it landed better for arriving out of genuine necessity rather than as an abstract addition to memorize.
Vamsi, still finding his footing three weeks after joining properly, asked why we needed elif at all instead of just nesting more nested if statements the way we'd covered back in April. It's a completely fair question, since they can technically accomplish similar things. I explained that elif keeps a chain of alternatives flat and readable rather than sinking deeper and deeper into nested indentation with every new option, which is as much about a human reading the code later as it is about what the computer actually does with it. That distinction, code being written for people as much as for machines, is one I don't think I'd explicitly said out loud to this group before.
Eight kids now, spanning a wider skill range than the original five or six, and I'm realizing project-style sessions like tonight's actually handle that spread better than tightly scripted single-concept lessons do. Sneha and Divya can extend and complicate the assignment on their own initiative, Rahul and the newer additions can focus on getting the core version working, and everyone's working on some version of the same real thing rather than the faster kids simply waiting around for the rest of the group to catch up. I want to build more sessions around this shape going forward, fewer isolated topics, more small projects that let the range in the room work in its favor instead of against it.
I'm aware this is roughly the same lesson I keep re-learning in slightly different forms all year, going back to the pacing mistakes of the very first month. Match the shape of the lesson to the shape of the group in front of you, rather than the other way around. I don't think I'll ever finish learning that lesson completely. I just seem to get a little better at applying it each time it comes back around.
Statistics again tonight, picking up from mean, median, and mode back in April and May: standard deviation, or more precisely, the idea behind standard deviation without the actual formula, since computing it by hand isn't a useful use of anyone's time at this stage and would probably do more to obscure the concept than clarify it.
I set up a comparison using, predictably, cricket scores again: Team A scored 20, 20, 20, and 20 across four matches. Team B scored 5, 35, 15, and 25. Both teams average exactly 20 runs. I asked the group whether that means the two teams are basically the same, and got an immediate, near-unanimous no, which is exactly the intuition I wanted to build on.
Divya put it well without any prompting: Team A is completely predictable, always exactly 20, while Team B is wild, sometimes terrible, sometimes great, averaging out to the same number almost by coincidence. That's standard deviation, in plain language, before I'd introduced the term at all: a measure of how spread out the numbers actually are around that average, not just what the average happens to be.
This connects directly back to the "which measure is right" conversation from the cricket statistics activity during the trip. A single average can flatten two very different realities into the same number, and standard deviation is the tool for noticing when that's happening. Sneha asked whether a low spread is always better than a high one, and I told her it depends what you're measuring: consistency is great for a bowler you want reliability from, but a batsman capable of occasionally huge scores might be worth more on average even with a wilder spread. There isn't a universally correct answer, just a more complete picture once you're looking at spread and not only the average.
Naveen, predictably, wanted to know which kind of player he'd rather be, and I turned the question back on him rather than answering it myself. He decided, after some genuine back and forth with Vamsi, that he'd rather be Team B, the wild, high-variance one, because "boring is worse than losing sometimes," which is a very Naveen answer and also, honestly, not a bad philosophy for a certain kind of cricket player. I didn't correct it. There isn't a wrong answer to that particular question, only a more or less self-aware one, and his struck me as pretty self-aware for someone who usually leads with jokes first.
We closed by looking back at a couple of the bar charts from earlier sessions with this new lens: not just reading off the tallest bar or the average height, but actually noticing how much the bars vary from each other. I think this is a genuinely useful habit to plant now, conceptually, well before anyone needs the actual formula, which I suspect most of this group won't encounter formally for a few more years of schooling. Better to have the intuition ready and waiting than to meet the formula cold with no sense of what it's supposed to be describing.
Sneha closed the session by asking whether Python could calculate all of this for us automatically once we knew where to look, rather than working it out by hand every time. I told her yes, and that we'd get there eventually, but that I wanted the intuition to exist first, built by hand, before handing it over to a function that does it invisibly. I think there's a real risk in teaching the shortcut before the understanding: it's easy to call a tool correctly without knowing what it actually means, and I'd rather this group be able to explain why a number matters than just produce it on command.
Tonight I gave the group less than usual: a one-sentence prompt, build a number guessing game, and let them figure out the rest themselves rather than walking through a template together first. This is the closest thing to a real, independent project this series has attempted, and I wanted to see what would happen without me scaffolding every step.
For most of the session, I circulated rather than lectured, which felt like a genuinely different role than I've played in almost any prior session. Divya built hers with a while loop and a tries counter almost immediately, clearly drawing on the mini project from the trip back in June, and had it fully working, including a "too high, too low" hint system, faster than I expected anyone to finish.
I stayed mostly quiet for the first stretch, resisting the urge to jump in whenever someone hit a wall, since the whole point of tonight was seeing what they could do without me narrating every step. That was harder than I expected. Watching someone stare at a blank editor for a full minute and saying nothing takes more restraint than actually teaching does.
Rahul struggled longer than most, and rather than stepping in immediately, I let Sneha, sitting two tiles over on the call, walk him through it once she'd finished her own. Watching her explain while True as "keep asking forever until something inside the loop tells it to stop" was a cleaner explanation than the one I originally gave back in April, and I told her so directly, which she seemed genuinely pleased about.
Naveen built a cricket-themed version, obviously, guessing a bowling speed instead of a random number, and added something none of the others thought to include: a running tally of total guesses across multiple rounds, essentially inventing a basic score-tracking system on his own initiative. It's not the assignment I gave. It's a better one, in some ways, and I'd rather see that kind of unprompted extension than strict adherence to my original, narrower prompt.
Priya and Lakshmi, still a newer pairing than most of the group, worked through theirs together again, the same way they had during the mini project back in Kompally, and finished with a version that congratulated the player differently depending on how many guesses it took, "lucky," "solid," or "that one took a while," which is a small but genuinely thoughtful bit of design neither of them had been taught. I'm increasingly convinced that pairing them together wasn't a one-time fluke from the in-person visit. It's just how they work best, and I want to keep giving them the chance to.
Every single kid on the call tonight built something that combined input, loops, conditionals, and randomness, essentially the majority of a year's curriculum, into one working program, with meaningfully less direct guidance from me than any prior session. That's the real milestone here, not the guessing game itself, which is a simple enough program on its own. Eight months ago, most of this group couldn't reliably get Python installed without my walking them through each dialog box. Tonight, I mostly just watched.
I told them that directly before we signed off, plainly, without dressing it up: eight months ago, most of you needed help with every single line. Tonight, most of you needed help with none of them. Divya said, half-joking, that I was going to run out of things to teach them soon. I told her that's a good problem to have, and one I'm nowhere near close to actually running into.
I planned tonight as a tight, forty-minute mixed review covering trigonometry and statistics, with time left over to preview whatever comes next. It ran the entire session and slightly over, not because anyone was confused, but because almost every question pulled a thread from somewhere else in the curriculum, and I kept letting those threads run rather than cutting them off.
I introduced the unit circle tonight for the first time, as a slightly more general way of thinking about sine and cosine than the right-triangle version from back in March, showing how the same ratios extend smoothly as an angle sweeps all the way around a circle rather than staying locked inside one triangle. Sneha connected it almost immediately to the kite problem from March, asking whether a kite string at a very steep angle, close to straight up, would mean sine gets close to its maximum possible value. Correct, and a genuinely sharp extension of an example from four months ago that I hadn't expected anyone to still be actively drawing on.
Vamsi, quieter through most of this topic historically, asked what happens to cosine at that same steep angle, working out on his own, mostly correctly, that it should shrink toward zero as sine grows toward its maximum. I hadn't planned to cover that relationship explicitly tonight, the way sine and cosine trade off against each other as an angle sweeps around, but it came up naturally enough from his question that I let the review expand to cover it properly rather than saving it for some hypothetical future session.
Moving to a statistics recap, mean, median, spread, Naveen asked whether you could calculate an "average angle" the same way you'd calculate an average score, which is a much more interesting question than it might sound, and one I hadn't specifically prepared for. I gave a partial answer, that it's more complicated than a normal average because angles wrap around in a way plain numbers don't, and admitted I'd need to think about how to explain that properly rather than improvising something potentially wrong on the spot.
I like that I had to say "I need to think about that" out loud tonight instead of bluffing an answer. I've said a version of that phrase a handful of times across eight months now, and every single time, I think it's done more good than harm. It tells this group, implicitly, that not knowing something instantly is a normal, unremarkable part of thinking rather than a failure to hide. I'd rather model that honestly than perform an expertise I don't actually have on every possible tangent.
I could have cut both of those tangents off to stay on schedule. I didn't, mostly because they were exactly the kind of unprompted, cross-topic connections I've been hoping to see more of since around the algebra-graphing session back in February, and shutting them down to hit a planned stopping point felt like the wrong tradeoff. The cost is that tonight ran long and I didn't get to previewing what's next at all.
If I'm going to keep letting good tangents run, and I want to, I need to stop planning reviews with tight time budgets in the first place. A review session with this group, eight months and two subjects deep, was never going to be a quick forty-minute pass. I should have planned for the full hour from the start and been pleasantly surprised by extra time, rather than planning for extra time and running consistently over. Small planning mistake, easy enough to fix, and honestly a good problem to have this deep into the year.
Thirty-eight classes since a shaky Zoom call on a Sunday evening last November, five kids clustered around one shared laptop, a connection that dropped twice before we'd even said hello properly. I wanted this post to be a real look back, not just a status update, because eight months in feels like the right distance to actually see the shape of the thing rather than just the most recent week of it.
The Python side runs from a first, nervous print("Namaste, world!") through variables, input, strings, numbers, booleans, conditionals nested two layers deep, both kinds of loops, functions, and finally, in the last few weeks, small independent projects, a calculator and a full number-guessing game built with meaningfully less hand-holding than anything from the first few months. The math side runs from balancing simple equations through inequalities and graphing intuition, into trigonometry from right triangles up through the unit circle, and statistics from mean and median through a real, working intuition for spread and standard deviation, all without ever needing the formulas that would probably have gotten in the way this early.
Eight months, compressed into ten stops. It reads faster than it lived.
I came into this thinking I mostly knew how to teach, since I'd already been running KidCode's Python bootcamp for a while before this started. Some of that transferred directly. A lot of it didn't, or needed real adjustment: teaching around unreliable infrastructure instead of assuming a clean setup, reading engagement without native fluency in the room's first language, and the biggest one, learning that a quiet student isn't necessarily a confused one, which took me embarrassingly long to actually internalize about Kiran specifically. The two and a half weeks in Kompally taught me more about these kids, and about teaching in general, than I think any equivalent stretch of Zoom sessions could have on its own, but the Zoom sessions are what made the in-person visit mean anything at all. Neither one works as well without the other.
I also think, looking back, that this project has quietly changed how I run the KidCode sessions back home, more than I noticed happening in the moment. I check for understanding more often than I used to. I'm slower to assume a nodding head means a landed idea. I ask who wants to explain a concept back to the group instead of just asking if everyone's following. None of that started here, exactly, but all of it got sharpened here, tested against a much less forgiving set of constraints, a shakier connection, a real language gap, one shared laptop instead of one per student. Teaching gets better under constraint, I think, or at least mine has.
Divya still answers first and still explains things to other kids without being asked. Naveen still finds a cricket angle in literally everything, and I've stopped even slightly trying to redirect that impulse. Kiran talks now, genuinely, regularly, once a group setting stopped feeling like a place where being wrong was a visible risk. Rahul is still working a little harder than most to keep pace, and I'm still adjusting how I teach to make that gap smaller rather than just noting that it exists. Sneha asks the question that reframes the whole lesson roughly once a month, on a schedule I can't predict but have started to expect. Priya, quiet in November, explained standard deviation to the group better than I did a few weeks ago. Lakshmi and Vamsi, both people I only met because I got on a plane, are just regulars now, full stop, like they were always part of this.
I don't have an end date for this. There's no cohort graduating, no fixed curriculum with a final chapter I'm counting down toward. I want to keep going roughly the way we have been: weekly, Sunday evenings, Python and math interleaved, projects growing a little bigger and a little more independent as the group's foundation gets sturdier. I'd like to go back to Kompally again, though I don't have a date for that yet either. For now, there's next Sunday, and whatever the shared laptop and the WiFi decide to do to me this time. Eight months in, that's still, somehow, the part I look forward to every week.
If I'm honest, I started this back in November mostly wanting to be useful, in whatever small way I could manage from a laptop screen on the other side of the world. Eight months and thirty-eight classes later, that's still true, but it's stopped being the main reason I keep showing up. I keep showing up because I want to know what Naveen turns the next lesson into, whether Kiran speaks up again this week, whether Rahul's caught up enough yet to stop needing the extra explanation, whether Divya's already read ahead. This stopped being a volunteer commitment somewhere in the last eight months and became something closer to just a part of how my week is shaped. I don't have a tidy way to end this post, so I'm not going to try. There's a Zoom link going out this Sunday, same as always, and I'll write about whatever happens next time.
Day four felt like the first session with real momentum behind it. Attendance has climbed a little each week, and today I noticed something I hadn't quite clocked before: I recognized most of the room. Familiar names asking the kind of questions I've started to expect from them, sitting next to two or three students I hadn't seen before who'd heard about KidCode from a friend. The class felt more interactive than any session so far. More hands went up, and more people jumped in before I'd even finished asking a question.
The opening slide for day four. Four sessions in, this is the first topic that actually lets a program make a choice.
Today's topic was if statements, the first real branch point in everything we've covered so far. Booleans and comparison operators from the last two sessions weren't standalone ideas anymore. They were finally the thing a program uses to make a choice. I opened with a simple framing: right now, every program the class has written runs every line, every time, in order. An if statement is the first tool that lets a program skip lines depending on whether something is true.
The opening example: check the weather, decide whether to grab an umbrella. This slide ended up mattering more than I planned.
From there we broke an if statement into its three pieces: the condition, which is the yes-or-no question, the colon that starts the block, and the indentation that marks what actually sits inside the if. Naming the parts explicitly seems to help more than jumping straight to examples, since two of the three most common mistakes today, it turned out, came from students treating the colon and the indentation as formatting suggestions rather than rules.
About fifteen minutes in, a student asked the question I should have seen coming: what happens if the condition is false? Looking at my own slide, I realized I'd built the entire if-statement introduction, umbrella example included, without ever writing an else block into the presentation. I'd covered it in my head while prepping and then just never put it on a slide.
Rather than stall and go build something new on the spot, I used the example already on the screen. If it's raining, you bring an umbrella. If it's not, you don't, you just leave the house like normal. That's an else. I wrote it out live, an if block for the umbrella and an else block underneath for the version where nothing special happens. It's a fairly low-stakes save as far as mistakes in front of a class go, but it's worth noting because it's a good reminder that the plan is a starting point, not the whole job. A few students said afterward that the umbrella example made else click faster than anything else that day, which is a little humbling given it wasn't supposed to be there at all.
We spent real time on indentation specifically, since it's the part of Python that trips up beginners the most and looks the most like a formatting choice until it isn't. I ran a side-by-side comparison: the same two lines, once correctly indented and once not, with the second version throwing an IndentationError. Watching students react to that error message was useful. A couple of them clearly expected Python to figure out what they meant, the way a person reading the same code would, and the fact that it doesn't was a genuinely new idea for them.
The indentation comparison slide. This came back later in a different form during the roller coaster exercise.
I kept checking in throughout the lesson: quick show-of-hands questions, thumbs up or down on whether an example made sense, asking someone to explain a line back in their own words before moving on. It slows the pace down slightly, but after day two taught me what happens when I skip this, I'd rather lose a few minutes confirming understanding than lose the back half of the class to confusion nobody mentioned out loud. Comprehension held up well today. Almost every check came back with more thumbs up than not.
The exercise that ended up teaching the most wasn't one I'd planned as a debugging lesson. I asked students to write a program that asks for someone's age and prints whether they can ride a roller coaster: ask for age, and if they're 10 or older, print that they can ride. One student shared her screen to walk through her attempt, and it had three separate mistakes at once. The if line was missing its colon, the condition itself was backwards, checking whether age was less than 10 instead of at least 10, and the print statement wasn't indented under the if at all.
The roller coaster exercise. Simple on the slide, and the source of the best ten minutes of class.
Instead of fixing it for her, I went through each problem one at a time and asked the class to help me spot it before I said anything. The missing colon was the first one someone caught. The indentation issue came next, once I asked what would happen if Python couldn't tell which line belonged inside the if. The backwards condition took the longest, because the code still looked reasonable to a few students until we actually stepped through it with a specific age and watched it print the wrong thing.
That last part is exactly why I think this was the most useful stretch of the class. Writing correct code on the first try doesn't teach you nearly as much as fixing code that's wrong for three different reasons and having to isolate each one. A couple of students said this was the first time they'd really debugged something instead of just rewriting it from scratch until it happened to work, which is precisely the shift I want to see.
The exact three mistakes from the roller coaster bug were already sitting on this slide, coincidentally, right where we needed them.
A few other things stood out. When I put up the "predict what this prints" slide, more students called out answers before I'd finished reading the code aloud than in any previous session, and most of them were right. One student asked, unprompted, whether if statements were the reason apps show different content to different people, like an age check before letting someone watch something, which turned into a short but genuinely good tangent about where conditional logic shows up outside of a classroom. Two students who finished the password-checker exercise early turned around and started helping the student next to them instead of waiting for me to get to their table.
By the end of the temperature exercise, when I asked who wanted to share their output, more hands went up than I had time to call on. That hasn't really happened before this session.
Next class will look different from the first four. I'm planning the group's first small project, something that pulls together variables, input, booleans, and if statements into one program students build over the full session rather than a series of separate exercises. I also want to run a short quiz beforehand, low stakes, mostly to give students a concrete way to see what's actually sticking after four weeks rather than relying on my read of the room.
Four classes in, the thing I notice most isn't any single concept landing well. It's the shape of the room changing: more volunteers, more students finishing early and turning to help someone else, more questions that go slightly past what I asked. The else statement I forgot and the roller coaster bug a student accidentally built were both mistakes, mine and hers, and both ended up teaching more than anything I'd actually planned. Going into the project and the quiz next week, I want to hold onto that: the plan matters less than what happens when it doesn't quite go to plan.
I finished assembling the first complete draft of the manuscript today — Introduction, Methods, Results, Discussion, and Conclusion, all in one document, for the first time since I started this project fourteen months ago. It's rough. There are sentences I'll rewrite, a couple of figures that need to be redone at higher resolution, and at least one Methods paragraph Joe already flagged as too dense. But it's a real draft of a real paper, built entirely from work I did and understood, and I want to take a minute to look back at how it got here before diving into revisions.
In May 2025 I didn't know what a research question was supposed to look like, let alone have one. The project moved through a real sequence of refinements: a vague interest in healthcare NLP became an interest in speech and cognition specifically; a mood became three candidate research questions; three candidate questions became one, grounded in an actual dataset — the DementiaBank Pitt Corpus — once I understood why longitudinal data mattered for a trajectory-based question rather than a snapshot-based one. Feature engineering went from word counts, to handcrafted discourse and coherence features, to SBERT-based semantic embeddings, to coreference and entity-tracking features that, in the end, mattered less than I expected them to. Modeling went from an LSTM that was badly mismatched to the dataset's size, to Random Forest, to LightGBM with Optuna-tuned hyperparameters and patient-grouped cross-validation, ending in a five-model fold ensemble.
The two hardest technical turning points were not the ones that felt like progress at the time. Discovering the patient-leakage bug in my cross-validation splits in October 2025 meant watching my numbers get worse right after I'd "fixed" something — correctly worse, but still uncomfortable. And finding the silent entry-MMSE bug in December — a renamed column and a string/integer ID mismatch, both quietly defaulting to a group median for months — was humbling in a different way: the single most predictive feature in the entire project had been effectively broken the whole time I was trying to interpret results without it. Both of those moments taught me more about doing careful research than any of the moments that felt like wins.
The final model — a patient-grouped, five-fold LightGBM ensemble over clinical, handcrafted discourse, and SBERT-embedding features — reaches a held-out test R² of 0.732 (RMSE 3.35, MAE 2.22, Pearson r 0.867), with a 5-fold cross-validation estimate of R² = 0.840 ± 0.015 on the development data. The honest scientific finding underneath those numbers isn't that speech alone can predict cognitive decline — it's that clinical baseline assessments are the dominant predictor of future MMSE, and that discourse and semantic speech features contribute a smaller, consistent, cross-validated improvement on top of that. That's a narrower claim than the one I set out chasing in May 2025, and it's the one I can actually stand behind.
The title page of the first complete draft — fourteen months after the project started as a vague interest in healthcare AI.
Looking back at fourteen months of posts, the throughline isn't a straight line at all — it's a series of corrections. Simpler models beating a more sophisticated one. A leakage fix that made results worse but more honest. A silent bug that had been quietly capping performance for months. A hypothesis (speech features as the primary signal) that had to be narrowed once the ablation evidence came in. None of these felt like progress in the moment. All of them were necessary to end up with a result I trust.
The draft still has real gaps. The Methods section needs another editing pass for density. A couple of figures need to be regenerated. And I know the Discussion's limitations section will need to expand once outside readers — starting with Joe, and eventually reviewers — start asking questions I haven't thought to ask myself yet.
Revision, not new research, for now: tightening the writing, regenerating figures, and getting outside feedback on the draft. Looking further out, the natural extensions are ones I can only gesture at with this dataset: validating on a larger and more contemporary longitudinal cohort, incorporating other biomarkers alongside speech (acoustic features, gait, or other passively collected signals) rather than speech alone, exploring temporal architectures better suited to irregular, sparse visit intervals than the LSTM I tried in August 2025, and — most importantly — testing whether any of this holds up on data collected outside of the Pitt Corpus entirely. This draft is a real milestone, not a finish line.
After day two, I rewrote the lesson plan for day three almost completely. Instead of trying to cover as much ground as possible, I picked four ideas that all point at the same underlying concept: booleans, comparison operators, and, and or. I gave the entire hour to just that. No for loops, no while loops, nothing held over from the topics that had gotten rushed the last time. Fewer moving pieces, more time on each one.
The opening slide for day three. After the last session, I cut the lesson down to a quarter of the topics.
We opened with a quick review of int(), float(), and the arithmetic operators from day two, then moved into booleans properly, not as a rushed setup for if statements but as their own topic. I put up simple expressions, 5 == 5, 10 > 20, "cat" != "dog", and gave the class real time to sit with each one before revealing the answer. Nobody was racing to the next slide. That alone changed how the hour felt.
From there we went into comparison operators, then and and or, treating each one as worth a few extra minutes rather than a line to get through. I kept coming back to the same framing: and needs both sides to be true, or only needs one. Repeating that a few different ways, with a few different examples, is not exciting to plan, but it's what day two was missing.
One of the students who joined that day was younger than the rest of the group, around third grade, and new to the bootcamp. During one of the interactive examples, I asked the class to explain the difference between and and or in their own words, and she was the one who raised her hand first. She said and needs both things to be true, and or just needs one of them. Then, without me prompting her further, she came up with her own example to show it. Watching someone that young reason through boolean logic correctly, and unprompted, told me more about whether the slower pace was working than anything else that happened that day.
The and/or/not slide, the one we spent the most time on and the one that stuck.
The difference from day two showed up almost immediately in how many students were willing to answer out loud. When I put up a comparison and asked the class to predict True or False before I revealed it, more hands went up each time, and a few students started arguing quietly with each other about the answer before either of them said it aloud. That's a good sign: it means they're reasoning it through rather than guessing.
A few students started explaining things to each other without me stepping in. When one student got stuck on why age > 5 and age < 18 evaluated to True for a specific number, another student jumped in with an explanation before I had the chance to. I let it run instead of cutting in. Students were also more willing to read code out loud than they had been in earlier sessions, with less hesitation over the syntax and more focus on what the line actually meant.
I closed the lesson with a small challenge: write a password checker that asks for a username and password, and prints True only if the username is "admin" and the password is "kidcode123." It's a direct application of and, dressed up as something that feels like a real program instead of a textbook exercise. A handful of students said they wanted to try extending it on their own, adding more usernames or a limited number of attempts, before we'd even finished going over the intended solution.
The goal was never just to get through a curriculum. It's to get students to a point where they trust their own reasoning enough to try things without waiting for permission. Day two taught me that cramming four topics into an hour doesn't actually teach four topics. It teaches a fraction of one and leaves the rest as vague impressions. Day three, with a quarter of the material and four times the attention on each piece, is what I want more of. A third grader working out and versus or on her own is a better outcome than a room that nodded along to twice as many slides.
Attendance for day two was close to day one. Most of the same students logged back onto the Google Meet, plus one new face who'd heard about the bootcamp from a friend. Before getting into new material, I had a few students walk through their homework from day one out loud: a short program that calculates what age you'll be five years from now. Almost everyone had it working. The couple who didn't were missing a closing parenthesis and, in one case, had left out int() entirely, which we fixed together in a few minutes.
The plan for day two was booleans, if statements, for loops, and while loops. Four topics, one hour. I want to be honest about this now: that was too much. Each of those ideas is worth sitting with on its own, and stacking all four together in a single session meant students were still absorbing one concept by the time I'd already moved on to the next.
The opening slide for day two. In hindsight, the title alone was a preview of how packed the hour would get.
I spent several hours beforehand building this presentation, because I wanted booleans and loops to feel visual and concrete instead of abstract rules to memorize. We started with booleans, since if statements and loops both depend on them. I put a handful of expressions on screen: 5 > 3, "cat" == "dog", 10 == 10, and asked students to call out True or False before I revealed the answer. That part went well. By the third or fourth example, more hands were going up, and a couple of students started predicting correctly even on the trickier ones.
Where things slowed down was the move into if statements. A few students asked questions that showed they were genuinely trying to connect the new material to what we'd covered on day one. One asked whether an if statement could check a variable's value the same way we'd been printing variables, which is exactly the kind of connection I want students making on their own. Answering questions like that well takes time, though, and I hadn't built enough of it into the plan.
By the time we reached for loops and while loops, the pace was working against me. I introduced range(), walked through a for loop counting to five, then a while loop doing roughly the same thing with a counter variable, and asked students to notice the difference between them. A few caught it right away. More were still working through if statements and hadn't fully caught up.
Participation dropped compared to day one during this stretch. Students were still following along: cameras stayed on, nobody dropped off the call, but fewer people were volunteering answers. When I called on someone directly, several needed a moment before responding, which usually means they're still processing the previous idea rather than ready for the next one.
The if/else slide. This is roughly where the pace started outrunning the class.
Even with the pacing off, there were real moments worth holding onto. About twenty minutes after we'd moved past if statements, one student said something close to, "oh, so it's just like a question the computer asks itself," which is a better description of conditional logic than most explanations I could have written myself. Watching students start to think about programming as logical reasoning rather than a set of syntax to memorize is the actual point of teaching this material, and it happened here even with the pace off.
After I ended the call, a few students stayed on for several more minutes with questions about loops. One wanted to know if a while loop could run forever. Another was trying to figure out how to print only even numbers and wanted to talk through her thinking before trying it on her own. Sticking around after class to keep working through something that isn't fully clicking yet is a good sign, regardless of how the hour itself went.
The material I put together was solid. The problem was how much of it I tried to fit into sixty minutes. Teaching isn't just about how much ground you cover. It's about matching the pace of the people you're teaching. Day two was a reminder that four good concepts, poorly paced, don't land as well as two concepts taught with room to actually think them through. I went into day three with a much shorter list.
The Discussion section has taken longer to draft than Methods and Results combined, and I think that's because it's the section where I actually have to take a position rather than just report what happened. It's easy to describe a permutation importance table. It's harder to write a paragraph honestly explaining what it means that clinical features account for the overwhelming majority of predictive power, without either overselling the speech features' contribution or undermining the motivation for the entire project.
I went through a few drafts of the core interpretive paragraph before landing on something I'm satisfied with: the finding that clinical baseline assessments dominate isn't a failure of the speech-based approach, it's a reflection of how strongly autocorrelated cognitive trajectories are over the multi-year timescales in this cohort — and the more interesting scientific contribution is that discourse and semantic speech features add a small, consistent, cross-validated improvement on top of that strong clinical prior, which matters most precisely in settings where frequent clinical assessment isn't available. That's the sentence I wish I'd been able to write back in November, when the clinical-feature jump first made me anxious about the whole premise of the project.
Limitations took a lot of honest reflection. The dataset is small — 173 samples from 126 patients — and drawn from a single site, collected between 1983 and 1991, using elicitation tasks (like Cookie Theft picture description) that may not generalize to other populations, other languages, or more modern speech-collection methods like phone-based passive monitoring. The gap between cross-validated R² (0.840) and held-out test R² (0.732) also belongs here, explained plainly as an expected consequence of an iterative research process rather than glossed over. I also had to be honest that the coreference-based entity features, which took real effort to build back in August 2025, ended up contributing relatively little in the final feature importance ranking — not every feature I built earned its place, and the paper should reflect that rather than only showcasing what worked.
The four limitations that ended up anchoring the Discussion section, mapped out before I started writing prose.
Writing Discussion well seems to require holding two things in your head at once: genuine confidence in what the data supports, and genuine humility about what it doesn't. I don't think I was naturally good at doing both simultaneously going into this — my instinct under uncertainty is to either overstate a finding to make it sound more important, or undersell it out of caution. Neither is what the section needs.
Write the Introduction and Conclusion last, now that I know exactly what claims the rest of the paper actually supports, and then assemble a complete first draft end to end.
The first actual class had been on my mind for weeks. There's a gap between planning a curriculum and teaching it for the first time, and Saturday morning was when that gap closed.
I logged into Google Meet a few minutes early. Students started joining quickly—within a few minutes, twelve had made it in: Anuk, Jaswitha, Oviya, Sabera, Sumirah, Purnima, Arya, Rajani, Ann, Saji, Gowri, and Gowtham. Attendance was better than I'd expected, and almost everyone came prepared and ready to work.
The class as it looked from my end—twelve students on, cameras up, ready to start.
The first order of business was installation. Getting Python and Visual Studio Code running across twelve different computers, with different operating systems and permission settings, never goes exactly to plan. I'd set aside the first part of class specifically for this. A few students ran into issues—one had a permissions error during installation, another's VS Code wasn't detecting Python at all. While I walked through the steps on screen, other students who'd already finished setup helped troubleshoot. Both problems got resolved. We had everyone writing code within about twenty minutes.
We started with print() statements—the most direct way to confirm that Python is running and responding to what you type. Students wrote their first programs, ran them, and a few immediately started changing the text to see what else they could get Python to display. That instinct to experiment before anyone tells you to is a good sign early on.
From there, we covered data types: strings, integers, floats, and booleans. I explained why they matter with a concrete example—you can add two numbers together without any trouble, but if you try to combine a number and a piece of text the same way, Python stops and tells you something's wrong. Understanding that different kinds of data behave differently is one of those foundational ideas that prevents a lot of confusion later.
Variables came next. I described them as labeled containers—a place to store information so a program can refer to it again later without retyping it each time. We worked through several examples together, and I asked students to predict what would be printed before we actually ran the code. Getting them to think through what Python is doing at each step—rather than just watching it happen—made a noticeable difference in the follow-up questions.
The piece that clicked most visibly was input(). Up until that point, every program students had written just displayed something fixed. input() means the program can ask the user a question and do something with whatever answer it gets. That shift—from a program that only displays output to one that can actually respond—is when Python stops feeling like an exercise and starts feeling like you're building something real.
The first exercise was intentionally straightforward: write a program that prints your name, your age, and what you're most excited to learn during the bootcamp. The goal was to make sure everyone had print() working and could use it to display several things in sequence. Students shared their outputs aloud, which made for a good few minutes—hearing what each person wanted to learn gave the class a clearer picture of what everyone was working toward.
Students working through the exercises during class.
The second exercise introduced input(). Students wrote a program that asks the user for their name and age, then displays both back. It's a small program, but it required pulling together several things we'd just covered: storing input in a variable, then using that variable in a print() statement. Syntax errors came up—missing quotes, a mismatched parenthesis, a typo in a function name. We walked through each one as a group. I made a point of saying directly that finding a bug and fixing it is the same work as debugging real code. Getting comfortable with that process early is worth the time.
By the end of class, everyone had both programs running.
Before we wrapped up, I gave everyone a challenge to work on before our next session on Saturday, July 4: can your program tell the user how old they'll be in five years? It builds on everything we covered—input(), variables, and a simple arithmetic step. I want students to work through it themselves rather than waiting to be walked through it in class. Coming back with something they tried, even if it doesn't quite work yet, is more useful than coming back having done nothing.
A few students kept their cameras off early on, which is normal the first time. I made a point to call on different people throughout the session rather than defaulting to whoever was most visibly engaged. When someone wasn't confident about an answer, I encouraged them to guess anyway—being wrong out loud is easier when the room makes that okay.
The dynamic started shifting once students began helping each other. When someone ran into an installation issue, another student who'd already worked through something similar jumped in with what had worked for them. That's not something you can plan for—it just happens when people are paying enough attention to what's going on around them.
A few syntax mistakes turned into useful teaching moments. One student had forgotten to close a parenthesis; another had a quote mismatch that was hard to spot at first glance. We stopped and traced through each one rather than just correcting it and moving on, because reading an error message and working backward to its cause is exactly the kind of thinking that makes someone better at programming over time. I made sure to say that out loud—debugging isn't a sign that something went wrong, it's just part of writing code.
I spent a good amount of time building the curriculum before this class—thinking through the pacing, deciding what to cover first and what to save for later. Running the actual session made it feel less like preparation and more like teaching. That transition is what I'd been working toward.
Watching students write their first Python programs and have them actually work is a different thing from anticipating that it'll go well. The class confirmed the foundation is there. We'll build on it Saturday.
Avondale, The Heights at Westridge, and Ridgeview at Panther Creek are close to each other in the way that many neighborhoods are close—geographically near, socially separate. Students who live in these communities might go to the same stores, drive the same roads, and still never interact in any meaningful way. That gap is ordinary, and most of the time, nobody thinks much about it.
This initiative is my attempt to do something about that gap—not through a grand program or an institutional mandate, but through the smaller, more practical act of learning to code together.
A few weeks ago, I started thinking about whether there was a way to bring students from these three communities into the same room—or at least the same Zoom call—around something concrete. Coding felt like the right anchor. It's a skill with a clear entry point, a low barrier to starting, and enough depth that students at very different levels can still be working toward something real.
The core idea is simple: weekly Zoom sessions, every weekend, open to students from all three communities regardless of experience. Not a class in the rigid sense—more of a shared space to build, ask questions, and figure things out alongside people you wouldn't otherwise know.
The first meeting was introductory. I wasn't trying to accomplish much. I just wanted to see who showed up and what they were carrying with them.
Students who came out for the first KidCode Python Bootcamp session at the neighborhood pavilion.
Around eleven students joined. The first few minutes had the particular awkwardness of a Zoom call where nobody is quite sure who's supposed to talk first—the kind of silence that feels loud. I started with a quick roll call by community, which helped: hearing "Avondale," "Heights," "Ridgeview" repeated back was a small but useful way to make the three-way nature of the group tangible rather than abstract.
We went around with introductions: name, community, grade, and one honest answer to the question, what's your experience with coding, if any? The range was immediate and real. One student from Ridgeview had been writing Python for about two years and had built a few small projects on his own. Another from Avondale had done Scratch in fifth grade and hadn't touched anything since. Most attendees fell somewhere in the middle—a semester of a school CS elective, a few hours of a tutorial they'd started and not finished, or some exposure through a math class that briefly touched on programming.
Nobody exaggerated their experience, which felt like a good sign. There's a version of this kind of meeting where people perform confidence. This wasn't that.
Introducing concepts to the group during one of our early in-person sessions.
The first real discussion came when I asked the group a direct question: What do you actually want to get out of this? It sounds like an obvious thing to ask. In practice, it's the kind of question that separates a meeting from a check-in.
Answers varied. A few participants wanted to build something—an app, a game, something they could show people. One student from The Heights said she mainly wanted to understand what people meant when they talked about programming, because it came up constantly in school and she felt like she was missing a foundation everyone else had. Someone else said he was hoping to get better before AP Computer Science next year. One attendee was more candid: he said he mostly came because a friend had mentioned it and he was curious what this would actually be.
That last answer was probably the most useful. It acknowledged that not everyone arrives at something like this with a clear purpose—and that the initiative has to earn sustained engagement rather than assume it.
We spent some time brainstorming project directions. Ideas that came up: a shared community events app that people across all three neighborhoods could actually use, a simple quiz game that could be built incrementally over several weeks, a tool to track something—scores, weather, anything—just to have a functional goal to orient around. Nobody landed on a final choice. That was fine. The brainstorming wasn't really about the projects themselves yet. It was about getting people to think out loud together.
The most substantive conversation of the night came out of a tension I raised directly: if every session is pitched at beginners, the more experienced students will check out. But if the content runs at an intermediate level, participants who are just starting will fall behind quickly and disengage. Both outcomes undermine what I'm trying to build.
I didn't resolve it in that meeting. What I did do was name it clearly, which matters more than it sounds. The tentative direction—still being worked out—is to structure sessions so that there's a shared foundation everyone works through together, with room for students further along to take on more open-ended extensions of the same concept. Pair-based work across experience levels might help too. The real answer will come from seeing what works once sessions start.
There was also a quieter thread running through the meeting about what it means to learn something with people you don't know yet. Several students mentioned that their school's CS classes felt competitive—graded, ranked, stressful in a way that made them reluctant to admit confusion. The appeal of this kind of initiative, if it's run well, is that none of that applies. Participants aren't being evaluated. They can ask a basic question without it affecting anything except their own understanding.
The most striking moment of the meeting wasn't a discussion point. It was near the end, when I asked everyone to type into the chat one thing they were hoping to build or learn—anything at all, no pressure. Students who had been quiet the whole call participated. The answers were specific and genuine: I want to make a game with a character I designed. I want to understand what a function actually is. I want to build a website for my family's business. I want to learn enough to know if I want to keep going.
That last one—learn enough to know if I want to keep going—is worth sitting with. It's an honest way to describe being at the beginning of something you're not sure about yet. It's also, I think, the right attitude to have toward a first meeting. Nobody has to be certain. They just have to show up.
Starting this weekend, I'll be running weekly Zoom sessions every Saturday or Sunday—consistent enough to become a rhythm. The first few sessions will focus on Python fundamentals: variables, loops, functions, and basic logic. Not because Python is the only option, but because it's readable, the error messages are relatively human, and there are good free tools that work on any computer without setup friction.
The curriculum I put together for the KidCode Python Bootcamp sessions.
I've set up a shared Discord server for communication between sessions—questions, links, code snippets, and whatever else comes up. There's also a lightweight GitHub organization for students who are ready to start version-controlling their work, with no pressure for those who aren't there yet.
The goal by the end of the summer is for every student who stays engaged to have built at least one thing they feel ownership over—something they can point to and say they made. What that thing is will vary. That's fine.
Something worth being honest about: this kind of initiative is easy to talk about and harder to sustain. Getting eleven people onto a call is not the same as building something durable. The weeks where attendance drops, when content is harder than expected, when schedules conflict—those are the real test.
But the first meeting felt like a genuine starting point, not a performed one. The students who joined weren't there because they had to be. They were curious enough to show up, honest enough to say what they didn't know, and open enough to think about what they might want to build alongside people they'd never met.
That's enough to work with.
Spent this stretch actually writing prose for the first time, starting with Methods and Results rather than the Introduction, on Joe's advice — apparently it's common to draft these sections first since they're the most mechanical and the least dependent on getting the framing exactly right, and writing them first tends to clarify what the Introduction and Discussion actually need to set up and explain.
Methods turned out to be more work than I expected, not because the pipeline is complicated to describe, but because I had to decide how much of the year's worth of iteration belongs in the final writeup. I didn't try to narrate every dead end (the paper isn't a diary), but I did keep the methodologically important pivots: why patient-grouped cross-validation replaced sample-level splitting, why the feature set includes clinical, handcrafted linguistic, and SBERT-derived components together rather than any one alone, and why a five-model fold-ensemble is the final architecture rather than a single trained model. Leaving out the leakage story, in particular, felt wrong — it's not just a footnote, it's the reason the reported numbers can be trusted at all, so it gets real space in the Methods section rather than a passing mention.
Results was more straightforward to draft, mostly because by this point the numbers are stable and I'm not still second-guessing them: held-out test R² = 0.732, RMSE = 3.35, MAE = 2.22, Pearson r = 0.867, alongside the 5-fold CV estimate of R² = 0.840 ± 0.015 on the development data. I also included the ablation table from January — clinical alone, speech alone, and combined — because that comparison is central to the paper's actual claim, not a side note. Writing the Results section made me tighten a habit I'd been sloppy about in earlier drafts: stating exactly which evaluation protocol produced each number, since CV and held-out test numbers are not interchangeable and I don't want a reader (or myself, months from now) to conflate them.
An early draft of the Methods section, describing the cross-validation protocol and the final ensemble architecture.
Revised the outline once, after actually drafting these two sections, because writing Results made it obvious the Discussion needs its own explicit subsection on the CV-versus-test gap rather than a passing sentence — it's a real methodological point worth explaining on its own, not something to bury.
Deciding what counts as a limitation versus what counts as ordinary methodological detail is harder than I expected. The small dataset size, the single-site 1980s–90s cohort, and the modest absolute size of the speech-feature contribution all feel like they belong in Discussion, but each of them also shapes how the Methods section should be worded, so there's real back-and-forth between sections rather than a clean linear writing order.
Draft the Discussion — the section I'm least experienced writing, since it requires arguing about what the results mean, not just what they are.
Almost a full year after I first started reading about domains, tasks, targets, and data, I sat down this week to start turning all of this into an actual manuscript, and immediately ran into a problem I hadn't anticipated: I don't really know how to write a research paper. I know how to run experiments, keep notes, and argue with myself about whether a result is real. I don't have much practice putting that into the specific, conventionalized shape a paper is supposed to take.
Joe walked me through the standard structure — Introduction, Methods, Results, Discussion, Conclusion — and, more usefully, through why each section exists and what job it's doing, rather than just the labels. The Introduction has to earn the reader's attention by motivating the problem before mentioning a single number. The Methods section has to be detailed and honest enough that someone else could, in principle, reproduce what I did, including the parts that didn't work if they're relevant to interpreting the parts that did. Results should present findings without yet arguing for what they mean. Discussion is where interpretation, limitations, and honesty about what the data can and can't support all belong. This sounds obvious in the abstract, but looking back at fourteen months of blog posts, I can see how much of what I've written mixes all of these together constantly — which is fine for a running journal, but not for a paper.
The manuscript outline Joe and I settled on — five sections, each doing a distinct job.
The hardest part so far isn't any individual section — it's deciding what the paper's actual central claim is. Eight months ago I would have said "speech predicts cognitive decline." I don't believe that's the honest headline anymore. The honest headline, based on the ablation work from January, is closer to: clinical baseline features are the dominant predictor of future MMSE, and discourse/semantic speech features provide a smaller but consistent complementary signal on top of them. That's a real finding, but it's a quieter one, and I've had to make peace with writing the paper I actually have evidence for rather than the paper I originally set out hoping to write.
Writing the Introduction forced me to state the research question more precisely than I ever have out loud: can longitudinal speech-derived discourse features, combined with clinical history, predict future MMSE severity — and separately, how much of that predictive power comes from speech versus from clinical history alone? Framing it as two connected questions rather than one is new, and it's a direct consequence of the ablation results.
Build a full outline before writing prose — section by section, with placeholder bullet points for what each subsection needs to argue and which numbers back it up — so I'm not making structural decisions and writing sentences at the same time.
I set aside a patient-grouped held-out test set this week — a slice of patients that took no part whatsoever in feature selection, hyperparameter tuning, or fold-ensemble training. Everything up to this point has been evaluated purely through cross-validation, which is a reasonable way to make modeling decisions on a small dataset but leaves open the possibility that repeated tuning decisions, made by me, looking at CV scores over and over across months, have quietly overfit to the CV folds themselves. A genuinely untouched test set is the only way to check that.
The final model: a 5-fold ensemble of LightGBM regressors, trained on the combined clinical, handcrafted discourse, and SBERT-embedding (PCA-reduced) feature set, using patient-grouped splitting throughout, with the noise augmentation and feature-binning tweaks from a few weeks ago. On the held-out test set, this model achieves R² = 0.732, RMSE = 3.35, MAE = 2.22, and a Pearson correlation of 0.867 between predicted and true MMSE. The 5-fold cross-validation estimate, computed over the remaining development data, is R² = 0.840 ± 0.015.
The gap between the CV estimate (0.840) and the true held-out test performance (0.732) is worth sitting with rather than explaining away. Some of that gap is expected and healthy — CV scores from a process that included iterative tuning decisions will tend to run optimistic relative to a truly untouched test set, even when leakage has been carefully controlled for, simply because you made dozens of small modeling choices in response to CV feedback over the past several months. Some of it is probably just small-sample variance — a held-out test set built from a limited number of patients has real sampling noise of its own. I don't think this gap indicates a bug the way the entry-MMSE issue did back in December; it looks like the ordinary, expected consequence of an iterative research process on a small dataset, and I'd rather report both numbers honestly than only report the more flattering one.
Predicted versus actual MMSE on the genuinely untouched held-out test set — R² = 0.732, MAE = 2.22.
I now have two numbers I'm comfortable standing behind: a cross-validated estimate that reflects the model's expected performance under the development protocol, and a held-out test estimate that reflects performance on data that genuinely never influenced any decision. MAE of 2.22 MMSE points, on a 0–30 scale with a clinical impairment threshold around 24, means the median prediction error is well within a single severity band.
Reporting a single cross-validation number from a long iterative process, without a genuinely untouched test set, risks reporting an optimistic estimate even without any leakage bug — the iteration itself is a subtle source of overfitting to the evaluation metric. This feels like an important thing to have learned before writing anything up, not after.
I think the modeling work, for now, is essentially done. Time to shift into writing this up properly — which is its own skill I haven't really practiced before.
Most of this stretch was incremental tuning, and in the spirit of documenting the real process rather than just the wins, I want to write down what didn't work as much as what did, because the list of rejected ideas ended up being long.
What actually helped: training five separate LightGBM models, one per cross-validation fold (each on that fold's ~80% training partition), and averaging their predictions at inference time rather than relying on a single model. Because each fold model sees a slightly different subset of patients, they make somewhat independent errors, and averaging reduces variance without adding bias — a small but consistent improvement over any single fold's model alone. I also added Gaussian noise augmentation to the training features (a small amount of random noise added to feature values during training, forcing the model to be less sensitive to exact feature values) and expanded the feature representation with binned copies of the top features (discretizing continuous features into quantile bins alongside their continuous form), both of which gave small additional gains on this small dataset.
The final ensembling approach: five fold-specific models, each trained on a different 80% of patients, averaged into one prediction.
What didn't help, in roughly the order I tried it: stacking (training a meta-model on top of the five fold models' predictions) — with only about 35 samples per validation fold, the meta-learner had nowhere near enough data to learn a sensible combination and overfit its own training signal; CatBoost as an alternative to LightGBM; adding several more demographic and clinical fields beyond the core set (hamilton scale, race, more detailed diagnosis subcodes); several other PCA dimensionalities for the SBERT embeddings; different noise levels for the augmentation; different top-k thresholds for feature selection; different correlation thresholds for pruning redundant features; and a 7-fold CV split instead of 5. Every one of these either did nothing or made cross-validated R² measurably worse.
A useful mental model I've settled into: on a dataset this size, almost every added degree of freedom is a bet, and most bets lose. The ones that won this round (fold ensembling, light noise augmentation, feature binning) all share a property — they're forms of regularization or variance reduction, not complexity addition. That fits the broader pattern from months ago, when a heavily parameterized LSTM lost to a much simpler Random Forest.
Current best configuration: 5-fold ensemble of LightGBM regressors, Optuna-tuned hyperparameters (shallow trees, strong regularization), trained on the combined clinical + linguistic + SBERT-PCA feature set with noise augmentation and binned feature copies, evaluated under patient-grouped cross-validation.
I've been treating cross-validation as my only evaluation signal for months now. Before I start writing anything up, I want to carve out a genuine held-out test set — patients the model never sees during any tuning or fold-averaging decision — so I have one number I can report with full confidence that no part of the modeling process was allowed to peek at it.
Ran the ablation study I've been planning since November, comparing four feature configurations under identical patient-grouped 5-fold cross-validation: linguistic (handcrafted) features alone, SBERT embeddings alone, clinical features alone, and combinations of all three. The results settle the question from a few weeks ago, at least as far as this dataset can settle it.
Handcrafted linguistic features alone are weak — cross-validated R² in the high-teens percent, at best a faint signal. SBERT embeddings alone do meaningfully better, somewhere in the mid-30s. Clinical features alone are the dominant single source by a wide margin, landing around 0.78 R² on their own — already close to what my full combined model was achieving a couple months ago before I'd even added speech. Combining all speech features (linguistic + SBERT) together gets to a little over 0.4, still far below clinical alone.
The part I actually care about is what happens when you add speech on top of clinical rather than comparing them as competitors. Clinical plus SBERT edges up a few points over clinical alone. The full combination — clinical, linguistic, and SBERT together — is the best configuration in every fold, consistently, not just on average. The gain from adding speech on top of a full clinical feature set is real but modest: a handful of points of R², not a transformation.
The full ablation: clinical features dominate on their own, but the combined model — clinical, linguistic, and SBERT together — wins in every fold.
I think this genuinely refines the research question rather than undermining it. The original framing was close to "can speech features predict cognitive decline." The more defensible framing, based on what I'm actually seeing, is something closer to: clinical baseline assessments are highly predictive of future cognitive status on their own, and discourse/semantic features derived from speech provide a smaller, consistent, complementary contribution on top of that. That's a less dramatic claim, but every piece of it is something I can actually back up with the ablation numbers, fold by fold.
Consistency across folds matters more than the size of an average effect. A gain that's small but present in all five folds is more trustworthy than a larger gain that only shows up in one or two folds — the latter is much more likely to be an artifact of which patients happened to land in which fold.
I want to push the combined model further with more careful hyperparameter tuning and some kind of ensembling, since the ablation suggests I'm not yet squeezing out everything the combined feature set has to offer, and also start thinking about how to frame all of this in whatever the eventual writeup looks like.
With the entry-MMSE bug fixed and the rest of the clinical feature joins audited and confirmed correct, I ran a proper permutation-importance analysis across all five cross-validation folds. The method: for each fold, take the trained model, measure its validation R², then shuffle one feature column at a time (breaking that feature's relationship to the target while leaving everything else intact) and measure how much R² drops. A bigger drop means the model was relying on that feature more. Repeating the shuffle multiple times per fold and averaging cuts down on noise from any single unlucky shuffle.
The results are fairly stark. Entry MMSE dominates, accounting for roughly a third of the model's total explained variance on its own — which lines up with the plain clinical intuition that a patient's baseline cognitive status is highly predictive of their future status, especially over the multi-year timescales typical in this cohort. Blessed Dementia Scale and baseline diagnosis category are the next most important, each contributing a much smaller but still real amount. The Mattis battery score contributes modestly. Somewhere further down the list, a couple of the top SBERT principal components show up — real, but clearly secondary to the clinical variables.
Native LightGBM split-based importance (how often a feature is actually used to split a tree, a different and cheaper-to-compute notion of importance) agrees with the permutation-based ranking on the top features, which is reassuring — it means this isn't an artifact of one particular importance method.
Permutation importance across all five cross-validation folds — entry MMSE dominates, with clinical features well ahead of speech-derived ones.
I think I now have an honest answer to the question I was uneasy about a few weeks ago: yes, clinical baseline information is doing most of the work. That's not the finding I originally set out hoping for, but it is very likely the correct finding, and pretending otherwise would just be bad science. The more useful and more honest question the project can actually answer is a narrower one: given that clinical features are dominant, do the speech-derived features add anything on top? That's a real, well-posed, checkable question, and — crucially — it's still a legitimate motivation for the project even if the answer turns out to be "a little" rather than "a lot." A speech-based signal that adds a modest amount on top of a full clinical workup is a much smaller claim than "speech can replace clinical assessment," but it's the claim the data can actually support, and I'd rather report that than overclaim.
I need to design the ablation carefully enough to actually distinguish "speech adds a little" from "speech adds nothing and this is noise." With a dataset this size, small R² differences between feature configurations could easily be within the range of cross-validation fold-to-fold variance rather than a real effect.
Run the clinical-only vs. speech-only vs. combined ablation, holding everything else (model type, hyperparameters, CV protocol) fixed, and pay close attention to the variance across folds, not just the mean, before drawing any conclusions.
While setting up the ablation study I'd planned last time, I went digging into why the "entry MMSE" feature — which I expected, based on everything I understand about this problem, to be by far the single most predictive feature available — wasn't behaving the way I expected in early feature-importance checks. It turned out to be almost inert, which didn't make sense.
I found two separate bugs, both silent, both data-joining issues of exactly the kind I worried about back in July when I was first matching transcripts to metadata:
The two silent bugs — a renamed column and a string/integer ID mismatch — that had been quietly breaking the single most predictive feature in the project.
mms → mmse1), and my lookup code was still requesting the old column name. Because I was using a permissive .get()-style lookup instead of something that would raise an error on a missing key, every single request for entry MMSE was silently returning nothing and falling back to a default — which, for a numeric feature, meant every patient was effectively receiving the same imputed value (the group median, around 22) regardless of their actual baseline score.1, 2, 3...), while the Excel-based metadata lookup used zero-padded strings ('001', '002'...). Every lookup was failing to match, again silently, again falling back to a default.Both bugs point the same direction: entry MMSE — the single feature I most expected to matter — had been carrying almost no real information for every model I'd trained up to this point. After fixing both issues so that each patient actually receives their true baseline score, this became, by a wide margin, the largest single improvement I've made in the entire project. Permutation importance analysis (feature by feature, measuring how much validation performance drops when a feature's values are shuffled) now shows entry MMSE as the dominant single predictor, well ahead of everything else.
This is a hard lesson, but a useful one: silent failures in data pipelines are more dangerous than loud ones. A crash tells you immediately that something is wrong. A .get() call that quietly returns a default value doesn't — it just degrades your data without telling you, and the model happily "learns" from whatever's left, producing numbers that look plausible enough that you don't necessarily go looking for a bug. I'd already been bitten by ID-matching issues once, back in the very first preprocessing pass in July, and I still managed to reintroduce a version of the same problem months later in a different part of the pipeline.
I now have to go back and be suspicious of every other feature I haven't specifically audited this way. If entry MMSE — the feature I most expected to matter — was silently broken for months, I have no strong reason to assume everything else is fine just because the pipeline runs without errors.
Audit the rest of the clinical feature joins with the same scrutiny, then actually run the ablation study I keep postponing — now on a version of the pipeline I trust considerably more than I did a week ago.
With patient-grouped cross-validation now in place as an honest baseline, I finally did the thing I'd been putting off since August: incorporated the clinical metadata that comes bundled with the Pitt Corpus alongside the speech data. Specifically, I added each patient's entry MMSE (their cognitive score at their first recorded visit), Blessed Dementia Scale score (a measure of functional impairment in daily activities), CDR (Clinical Dementia Rating), NYU and Mattis battery scores, and their baseline diagnosis category (dementia, control, or otherwise).
The jump in performance was immediate and large — considerably bigger than anything I'd gotten from weeks of feature engineering on the speech side. This is both encouraging and a little deflating. Encouraging because the overall pipeline now produces genuinely useful predictions; deflating because it raises an uncomfortable question about the actual thesis of this project. If clinical baseline information alone explains most of the variance in a future MMSE score, what exactly is the speech data contributing? Is this still a project about speech-based cognitive monitoring, or has it quietly become a project about the fact that cognitive decline is autocorrelated over time (which, put that bluntly, is not a very surprising finding)?
The jump in cross-validated R² after adding entry MMSE, Blessed scale, CDR, and baseline diagnosis to the speech-only feature set.
I don't have a satisfying answer yet, and I don't want to pretend I do. My working hypothesis is that clinical features and speech features are capturing genuinely different things — clinical scores are periodic clinician assessments, exactly the kind of infrequent snapshot the original motivation for this project was trying to get away from, while speech features could plausibly be sensitive to finer-grained or more frequent changes. But "plausibly" isn't evidence. The only way to actually answer this is to run the comparison properly: how much does the model gain from speech on top of clinical, versus clinical alone.
The feature matrix now spans three sources: clinical/demographic baseline features, handcrafted discourse/coherence features, and PCA-reduced SBERT embeddings. All are entering the same LightGBM model. I have not yet run a controlled ablation to separate their contributions — that's the obvious and necessary next step before I say anything stronger about what's driving these results.
I need to resist the temptation to declare victory just because the aggregate number went up. A dramatic performance jump from adding a strong feature is exactly the situation where it's easiest to stop being careful about what's actually being measured.
Run a proper ablation study — clinical-only, speech-only, and combined — under the same patient-grouped CV protocol, so I can actually quantify how much (if anything) the speech features are adding once clinical information is available.
I flagged this concern a couple weeks ago and finally sat down to check it properly, and it turned out to be a real problem, not a false alarm. My cross-validation setup had been splitting the dataset at the level of individual samples (patient-visits), not at the level of patients. Since many patients in this cohort have multiple visits, that means it was entirely possible — and, once I checked, actually happening — for one visit from a given patient to land in the training fold while another visit from that same patient landed in the validation fold.
That's a leakage problem. A model doesn't need to learn anything general about speech and cognition to do well under this setup; it can partly just learn to recognize a specific patient's writing/speaking style or baseline severity level from one visit and carry that recognition over to predict their other visit, since the two visits are highly correlated by virtue of being the same person. My cross-validation scores up to this point were, to some unknown degree, optimistic.
The fix is conceptually simple: switch to patient-grouped cross-validation (GroupKFold, grouping by patient ID), so that all of a given patient's visits are guaranteed to fall entirely within one fold — either all in training or all in validation, never split across both. This is a strictly harder evaluation setup, and I want to be upfront that my numbers got worse after making this change, not better. That's expected and, honestly, correct: the previous numbers were inflated by leakage, and this drop is the model losing access to information it was never supposed to have.
The difference between a leaky random split and a patient-grouped GroupKFold split — the same patient can no longer appear in both training and validation.
It's a strange feeling to "fix" your methodology and watch your reported performance go down. Joe was pretty clear with me about this being the right kind of setback, though — a leakage bug that inflates your numbers is much worse to discover after a paper is written than now, and this exact mistake (patient-level leakage in longitudinal clinical data) is common enough in the literature that catching it myself, at this stage, is a genuine positive.
Any evaluation involving repeated measurements on the same entity — patients, users, whatever the unit is — needs to be split at the entity level, not the observation level. This feels obvious written down, but it's exactly the kind of thing that's easy to get wrong when you're focused on getting a pipeline running end to end and haven't stopped to interrogate your own splitting logic.
Re-baseline everything under patient-grouped CV so I have an honest number to compare against going forward, and then revisit the clinical-metadata question from a few weeks ago — I suspect it's about to matter a lot more than it did before, now that my speech-only ceiling looks lower than I'd been telling myself.
Switched the modeling backbone from Random Forest to LightGBM, a gradient-boosted decision tree library. The core difference from Random Forest is how the trees are built: Random Forest trains many trees independently on bootstrapped samples and averages them, while gradient boosting trains trees sequentially, where each new tree is fit to correct the errors (residuals) of the ensemble built so far. In principle, this lets the model capture more subtle feature interactions and generally squeezes more performance out of tabular data — at the cost of being more sensitive to hyperparameters and more prone to overfitting if you're not careful.
That hyperparameter sensitivity is what led me to Optuna this week — a library for automated hyperparameter search. Instead of manually guessing learning rates, tree depth, number of estimators, and regularization strength, Optuna runs a sequential search (using a Bayesian-ish sampling strategy) that tries to find good hyperparameter combinations more efficiently than a grid search would, using the results of earlier trials to inform later ones.
The Optuna objective function used to search LightGBM hyperparameters against grouped cross-validation.
The early results with LightGBM plus Optuna, still on the speech-only feature set (handcrafted coherence features + PCA-reduced SBERT + temporal features), are in a similar ballpark to where Random Forest landed — noticeably not a dramatic jump. That's a little discouraging on its face, but it also isolates something useful: if switching from bagging to boosting doesn't move the needle much, the bottleneck is probably not "wrong model family," which pushes me back toward suspecting the bottleneck is the feature set itself, or specifically, the absence of clinical information I flagged a couple weeks ago.
Model family swaps (Random Forest → LightGBM) are cheap to try and worth trying, but they're not a substitute for actually diagnosing what's limiting performance. I went into this half-hoping LightGBM would just be better and mostly confirmed that it isn't going to rescue a feature-set ceiling on its own.
I also need to be honest with myself about my evaluation setup. I've been using a fairly standard cross-validation split up to this point, and I haven't carefully checked whether the way I'm splitting folds could let information about the same patient leak between training and validation — given that patients have multiple visits in this dataset, that's a real risk I've been ignoring, and I want to go back and check it properly before trusting any of these numbers too much.
Two things, in order: audit my cross-validation setup for patient leakage before drawing any more conclusions from CV scores, and seriously evaluate whether to bring in the clinical metadata fields I've been avoiding.
With the Random Forest baseline in place, I spent this stretch trying to squeeze more signal out of the longitudinal structure of the data rather than adding entirely new feature families. The instinct behind this: I've been treating each visit somewhat independently, but the whole point of longitudinal data is that a patient's history should inform the prediction, not just their most recent snapshot.
A few additions:
Separately, the raw SBERT embeddings were becoming a real dimensionality problem. Each transcript embedding is several hundred dimensions, and once I started concatenating embeddings across multiple visits per sample, the feature count was ballooning relative to the number of training examples — exactly the overfitting risk that burned the LSTM. I added a PCA step to compress the embeddings down to a much smaller number of components before they enter the feature matrix, trying to keep most of the semantic variance while cutting the dimensionality substantially.
Cumulative variance explained by PCA components on the 768-dim SBERT embeddings — 20 components retains most of the signal.
The feature matrix now has four rough categories: handcrafted discourse/coherence features, PCA-reduced SBERT embeddings, temporal/trajectory features derived from visit history, and (still) no clinical metadata. I re-ran the Random Forest baseline with this expanded set, and while there's some improvement, it's incremental rather than transformative — a few points of R², not a step change.
I'm noticing a pattern where each new feature addition gives a smaller marginal improvement than the last, which either means I'm approaching the ceiling of what speech alone can predict, or it means Random Forest isn't the right tool to actually exploit the richer feature set I now have. I don't have a clean way to distinguish those two explanations yet.
Joe suggested trying gradient boosted trees (specifically LightGBM) instead of Random Forest, since boosting tends to handle feature interactions and gets more mileage out of larger feature sets — it's worth ruling out "wrong model" before I conclude I've hit a ceiling on "not enough signal."
From August 27–30, 2025, I had the opportunity to compete in the TechLit Hackathon alongside Arjun Subramanian, Hrudhai Lothumalla, and Arjun Sharma. Over the course of a few intense days, we went from a rough idea to a fully functioning prototype—and ultimately placed first. More than the prize, though, the experience challenged how we think about building technology that is both practical and meaningful.
The ShopSmart dashboard: total savings of $1.49, list cost of $6.85, and an estimated 32-minute trip, alongside a live shopping progress tracker.
We built ShopSmart, an application designed to solve a simple but impactful problem: finding the most cost-effective way to shop across multiple stores. For many people—especially those on tight budgets or trying to shop more sustainably—comparing prices and planning efficient trips can be time-consuming and frustrating.
ShopSmart addresses this by allowing users to input a grocery list and receive an optimized shopping route. Depending on user preference, the app can prioritize the lowest total cost, the fastest route, or the most eco-friendly option. We also integrated an AI assistant that can answer questions about product prices and provide guidance tailored to the user's needs.
Our tech stack combined Python and JSX, supported by web scraping tools, the Google Maps API, and an OpenAI-powered language model for the chatbot. One of our main goals was to ensure that every feature worked cohesively—price aggregation, route optimization, and user interaction all had to come together seamlessly.
I contributed heavily to the development side of the project, focusing on implementing core functionality and ensuring that our backend logic effectively powered the user experience. I also contributed to the video narration—presenting the app and walking through its features for the final submission. Meanwhile, my teammates played key roles in refining the design and crafting a strong final presentation.
The Routes & Savings view: Cheapest, Fastest, and Eco tabs. The cheapest route shows a GPS path, $6.85 total cost, 32 minutes, 8.8 miles, and $4.33 in savings.
As with any fast-paced build, we ran into several technical obstacles. One of the biggest was creating a system that could successfully connect our pricing data with our route optimization logic. Because our approach relied on structured, context-aware data, ensuring that everything worked together in a meaningful and responsive way required multiple iterations.
We also encountered issues with the user interface—particularly our search functionality. While interacting with the AI assistant or adding items to the cart, the search bar would frequently reset, disrupting the experience. This forced us to rethink how users interact with the app and redesign parts of our input flow.
With limited time, we made deliberate trade-offs. We shifted focus toward high-impact features such as map rendering and calculating total trip costs across different store combinations, ensuring our core value proposition remained strong.
One of our biggest achievements was designing a solution that connects personal savings with environmental awareness. ShopSmart doesn't just help users spend less—it also gives them the option to make more sustainable choices in how they shop.
We're also proud of building a working prototype that brings together shopping lists, route optimization, pricing insights, and projected savings. Even with simulated data, we created a system that delivers personalized and actionable results, with clear potential to support underserved communities.
This experience reinforced how important it is to balance functionality, efficiency, and innovation—especially within tight time constraints. We gained hands-on experience integrating large language models into a practical application by building and connecting a chatbot trained on a custom dataset for grocery-related queries.
We also learned that usability comes first. Addressing interface issues made it clear that even strong technical features lose value if the user experience isn't smooth and intuitive. More broadly, we saw how fragmented grocery data can be—and how much opportunity there is to improve it.
Looking ahead, we want to integrate real-time pricing data so the app can reflect actual store conditions and adapt dynamically to changes such as inflation or shifting inventory. With more accurate data and continued development, ShopSmart has the potential to become a practical tool that helps people make smarter, more sustainable shopping decisions every day.
ShopSmart on the TechLit Hackathon DevPost gallery, listed as a winner alongside TinyTeacher and satya.
Winning first place—and earning $100 along with five .xyz domains—was an incredible moment for our team. But more importantly, it validated the idea that thoughtful, user-focused technology can make a real difference. Innovation isn't just about complexity—it's about building something that people can actually use.
I trained a Random Forest regressor on the same feature set the LSTM had access to — handcrafted discourse features plus SBERT embeddings, flattened per patient rather than fed in as a sequence — and it outperformed the LSTM by a meaningful margin, landing around R² ≈ 0.4 on cross-validation. That's still far from a result I'd call strong, but it's a real improvement over the neural network's ~0.44 validation score once you account for the fact that the LSTM's number came from a single validation split while the Random Forest number is a cross-validated estimate, and the Random Forest got there with a small fraction of the tuning effort.
This was a useful, slightly humbling result. I had implicitly assumed that a more "modern" architecture would be a strict upgrade over classical machine learning, and that assumption turned out to be wrong for this specific regime. Tree-based ensembles like Random Forest handle small tabular datasets well for a few reasons that are easy to state and easy to forget in the moment: they don't require gradient-based optimization over large parameter counts, they're naturally robust to irrelevant or noisy features (a tree can simply not split on a useless feature), and they don't need anywhere near as much data to find stable splits as a neural network needs to find stable weights.
A much smaller, simpler model outperforming a heavily parameterized LSTM on this dataset size.
The lesson I keep coming back to is: feature quality matters more than model complexity, at least at this dataset size. Swapping the LSTM for a Random Forest, with no other changes to the underlying features, moved performance in the right direction. That suggests the ceiling I'm currently hitting has more to do with what signal is actually present in my features than with which model architecture is consuming them. If that's true, the highest-leverage thing I can do next is improve or expand the feature set, not keep experimenting with architectures.
Current pipeline: parsed transcripts → handcrafted discourse/coherence features + SBERT sentence embeddings → per-visit feature vectors → Random Forest regression predicting MMSE at a target visit, evaluated with cross-validation. This is now my baseline to beat.
R² ≈ 0.4 leaves a lot of variance unexplained. I don't yet know how much of that is a ceiling imposed by dataset size and noise versus a ceiling imposed by missing information — specifically, I'm currently only using speech-derived features, and I have a nagging suspicion that a patient's own clinical history (not just their speech) is going to matter a lot for predicting their future score, and I'm not using it yet.
Two directions in parallel: keep expanding and refining the feature set (dimensionality reduction on the embeddings, temporal/trajectory features across visits), and start seriously considering whether to bring in the clinical metadata I've mostly ignored so far.
After weeks of feature engineering, I finally trained a model. Since the whole premise of this project involves patients with multiple visits over time, a sequence model felt like the obvious first choice — I built an LSTM that takes a patient's visit history as a sequence of feature vectors (handcrafted coherence features plus SBERT embeddings per visit) and predicts the MMSE score at a future visit.
The results were not good. Validation R² landed around 0.44, with an RMSE over 5.5 MMSE points — on a 0–30 scale, that's a big enough error band to be clinically close to useless. I spent a few days trying to convince myself this was a tuning problem: different learning rates, different hidden sizes, dropout, weight decay, a learning-rate scheduler that reduces on plateau. None of it moved the needle much.
Once I stepped back, the actual explanation was pretty obvious in retrospect. The Pitt Corpus is not a big dataset, and it's an even smaller dataset once you restrict to patients with enough repeated visits to form a meaningful sequence. My input feature vector per visit is large — SBERT contributes several hundred dimensions on its own, on top of the handcrafted features — and the LSTM I built has over half a million parameters. That's an enormous amount of capacity relative to maybe a hundred usable training sequences. I was essentially asking a model built for large sequential corpora to learn from a dataset better suited to a much simpler estimator.
Training and validation loss for the first LSTM baseline — validation performance plateaus well before training loss does.
Joe's read on this, which I now agree with, is that model complexity and dataset size have to be matched, and I had that badly mismatched. A sophisticated architecture doesn't rescue you from a small-sample regime — if anything it makes overfitting worse, since there are more parameters than there is signal to constrain them. This is a lesson I'd read about abstractly before starting this project but hadn't actually felt the consequences of until now.
The deeper challenge is that this dataset is never going to stop being small — DementiaBank isn't growing, and the number of patients with multiple usable longitudinal visits is a hard ceiling. That means the right modeling approach for this project isn't "get a bigger model," it's "get a model whose inductive biases are appropriate for a high-feature-count, low-sample-count tabular problem."
I want to try something much simpler next — a classical tree-based model on the same feature set, partly as a sanity check on whether the features themselves have any signal at all, independent of whatever the LSTM was or wasn't learning.
I lost most of two days this week to environment setup, which is not something I expected to be writing about in a research blog, but it felt worth documenting because it's such a normal part of doing this kind of work. Coreference resolution — figuring out that "she," "the woman," and "her" in a transcript all refer to the same entity — needs a fairly heavy transformer-based model, and getting a working coreference pipeline installed alongside the rest of my existing packages caused enough dependency conflicts that I ended up isolating it into its own virtual environment entirely, separate from the environment I use for everything else. Not elegant, but it works, and I'd rather have a slightly ugly setup than lose another day to it.
Losing two days to dependency conflicts before isolating the coreference model into its own virtual environment.
Once it was running, I used it to build a small set of entity-tracking features on top of the coreference chains: how often a referring expression fails to resolve cleanly, how many distinct entities get introduced across a transcript, and — the one I'm most curious about — entity transition entropy, a measure of how unpredictably the focus of a transcript jumps between entities from one mention to the next. My intuition is that healthy narration tends to move through entities in a fairly structured way (introduce the woman, talk about her for a while, introduce the sink, connect it back to her), while disorganized narration might jump around more erratically. Entropy is a natural way to quantify "how unpredictable is the sequence of entities being talked about."
I also finished off a batch of the remaining handcrafted features I'd sketched out weeks ago: filler rate is now computed properly rather than as a rough estimate, and intra-sentence repetition is tightened up to catch phrase-level repeats, not just single repeated words.
The motivating idea behind all of these features — coreference, entity entropy, repetition, filler rate — is the same one from my discourse-coherence reading: dementia can specifically affect a person's ability to maintain a topic, connect ideas across a narrative, avoid restating things they've already said, retrieve the right word, and keep track of which entity they're currently talking about. Each of these handcrafted features is meant to be a rough proxy for one of those specific underlying processes, rather than a generic "is this text weird" signal. Whether any of them actually correlates with MMSE is still an open question I haven't tested yet.
The coreference model is not particularly accurate on this kind of spontaneous, disfluent speech — it was trained on more standard written text, and picture-description transcripts full of false starts and fragments are a real distribution shift for it. I'm treating its output as a noisy signal rather than ground truth, which means the entity-based features probably have a real noise floor I can't fully get rid of.
I now have a genuinely broad feature set — lexical, semantic/embedding, coreference/entity, and disfluency-based. It's time to stop adding new feature families and actually try to predict something with what I have.
Handcrafted lexical-overlap features (how many words two consecutive sentences share) are a reasonable first pass at coherence, but they have an obvious blind spot: two sentences can be about the exact same thing while sharing almost no vocabulary at all ("the woman is drying dishes" vs. "she's wiping plates with a towel"). If I only measure literal word overlap, I'll systematically miss semantic coherence — coherence at the level of meaning rather than surface form.
This week I started using SBERT (Sentence-BERT), a transformer model fine-tuned specifically to produce sentence embeddings where semantic similarity in meaning corresponds to geometric closeness in the embedding space — unlike raw BERT embeddings, which aren't well-suited to direct comparison via cosine similarity out of the box. Practically, this means I can take two sentences, embed each one into a fixed-length vector, and get a similarity score that actually reflects whether they're about the same thing, not just whether they use the same words.
Computing SBERT embeddings and using cosine similarity between adjacent sentences as a semantic coherence feature.
With that in hand, I built a first batch of embedding-based coherence features:
I also went back and formalized a few of the more literal features I'd been computing ad hoc: lexical overlap (now computed properly rather than as a sanity check), intra-sentence repetition (a sentence repeating a word or phrase within itself), and filler rate (words like "um," "uh," and similar disfluency markers as a fraction of total words).
Looking at the numbers, semantic and lexical coherence measures are correlated but clearly not redundant — some transcripts score reasonably on lexical overlap while scoring low on embedding coherence, and vice versa, which is a mild but reassuring sign that these two feature families are capturing genuinely different things rather than one being a noisy version of the other. I don't yet know if either one actually predicts MMSE — that's still a few weeks away — but it's encouraging that they're not just duplicating each other.
Sentence segmentation on spontaneous, disfluent speech is messier than segmenting written text — people don't produce clean sentence boundaries when they're talking, especially under cognitive load, which is exactly the population I'm studying. I'm currently using a fairly naive punctuation/pause-based segmentation, and I suspect this is introducing noise into the sentence-level coherence features, but I don't have a better solution yet.
Coreference tracking and entity-level features are the next big piece of unfinished business from my discourse coherence reading — I want to measure how well participants track "who's doing what" across a transcript, not just how similar consecutive sentences are.
This week was almost entirely plumbing, and I mean that in the least glamorous sense possible: getting raw transcript files and a metadata spreadsheet into a single clean table I can actually build features on top of. It is not exciting to write about, but it's the part of research nobody tells you takes as long as it does.
The .cha transcript format encodes a lot more than just words — there are speaker-turn markers, timing information, and disfluency/overlap annotations layered into the text. My first task was writing a parser that pulls out only the participant's own speech (excluding the examiner's prompts and interjections) and normalizes it into plain text I can run further processing on, while still preserving enough structure to compute things like pause or repetition markers later if I need them. Getting this right took longer than expected because the annotation conventions aren't perfectly consistent across files — small formatting differences between transcribers.
An early version of exactly the kind of silent ID-matching bug that would come back to bite me again five months later.
The second, more annoying task was joining transcripts to the right metadata. Each patient can have multiple visits, and I needed to make sure a given transcript file was matched to the correct visit's cognitive scores — not the wrong one, and not silently dropped if the match failed. I did not get this fully right on the first attempt (a problem I suspect I'll be paying for later — matching problems like this have a way of resurfacing), but I now have a working pipeline that produces one row per patient-visit, with participant speech text and the corresponding MMSE and metadata fields attached.
At this point the pipeline looks roughly like: raw .cha files → parsed participant utterances → joined with the patient metadata spreadsheet on patient ID and visit number → a single dataframe with one row per visit. I started layering in the simplest possible handcrafted features on top of this — word count, sentence count, basic type-token ratio — mostly as a sanity check that the join is working and that features vary sensibly with visit and with diagnosis group, rather than as features I expect to actually matter in a final model.
The patient ID matching is more fragile than I'd like. Some IDs appear as plain integers in one place and as zero-padded strings elsewhere, and I already had to go back and fix a few silent mismatches this week where a row would just get the wrong metadata attached without erroring. I have a feeling this exact class of bug is going to bite me again later if I'm not careful, since it fails silently rather than loudly.
Now that I have clean text per visit, it's time to move past word counts and start building the actual discourse coherence features I've been reading about — lexical overlap, some notion of topic maintenance, repetition — and also start thinking seriously about semantic embeddings, since word-overlap-based features are unlikely to capture meaning-level coherence on their own.
Dataset search this week, and I think I found the right one: the DementiaBank Pitt Corpus. It's a collection of transcribed speech samples from participants completing a handful of standardized elicitation tasks — most notably the "Cookie Theft" picture description task, along with fluency, recall, and sentence-repetition tasks — collected from both a dementia group and a healthy control group. Critically for what I want to do, many participants were seen across multiple visits over time, with cognitive assessment scores (including MMSE) recorded at each visit.
That longitudinal structure is exactly what the last few weeks of reading convinced me I needed. A single transcript paired with a single MMSE score can only ever support a cross-sectional analysis — does speech at time T correlate with cognitive score at time T? But if a patient has transcripts and scores from visit 1, visit 2, visit 3, I can ask a more interesting question: does what they said at visit 1 (and how it changed by visit 2) tell you something about their score at visit 3? That's a prediction problem over time, not just a correlation at a single moment, and it lines up with the original motivation about wanting something closer to a continuous trajectory than a snapshot.
I spent a good chunk of time this week just understanding the structure of the data rather than doing anything with it. The transcripts are in the CHAT/.cha format used in the CHILDES/TalkBank ecosystem, which has its own transcription conventions (speaker tags, disfluency markers, overlapping speech annotations) that I'll need to parse carefully rather than just treating as raw text. Participant metadata — MMSE and related test scores, diagnosis, demographics — lives separately and has to be joined to the right transcript and the right visit, which already looks like it's going to be fiddlier than I expected, since visit numbering and file naming aren't always perfectly aligned.
The Pitt Corpus's directory structure, and an excerpt from a Cookie Theft picture-description transcript in CHAT format.
I now have a concrete, dataset-grounded version of the question I've been circling for a month: can machine learning models predict future MMSE severity scores using longitudinal patient speech transcripts and extracted speech coherence features? This is the version I'm going to actually try to answer, rather than continuing to refine it in the abstract.
The dataset is smaller than I initially hoped — this is not a "big data" NLP problem, it's a "small, valuable, real clinical data" problem, which Joe pointed out has real implications for what kinds of models are going to be appropriate later. I'm filing that away for when I actually start modeling.
Get the raw .cha files and the metadata spreadsheet actually loading and joined together correctly on my machine, even in the crudest possible way, so I have something to build feature extraction on top of.
This week was pure reading, and it was the first time the project started to feel like it had real technical bones rather than just a motivating story. "Discourse coherence" turns out to be a whole subfield with its own vocabulary, and a lot of it maps surprisingly cleanly onto the kinds of breakdowns that get described anecdotally in dementia case studies.
A few concepts I now understand much better:
Six terms from the discourse coherence literature that became the vocabulary for the rest of this project.
None of these concepts individually screams "dementia marker" — healthy speakers are disfluent, repeat themselves, and occasionally lose a thread too. The interesting question is whether the rate and pattern of these behaviors shift in a way that's measurable and gradual, rather than whether they're present or absent categorically. That reframing feels important: this isn't going to be a classification problem with a clean decision boundary, it's more likely a degree problem.
I'm converging on a working version of the research question: can machine learning models predict future cognitive severity scores using longitudinal speech transcripts and extracted discourse coherence features? The word "future" and "longitudinal" both matter here — I want the model to use a patient's history of visits, not just a single snapshot, echoing the original motivation about capturing trajectories rather than isolated points.
The obvious challenge is that all of this is currently just vocabulary, not features I can compute. Concepts like "global coherence" are qualitative descriptions in the linguistics literature, and turning them into something a model can consume (a number, or a vector) requires an actual operationalization choice, and I don't yet know which of the many possible operationalizations in the literature are the ones that end up mattering empirically.
Now that I have vocabulary, I need data. Time to look seriously at what longitudinal speech datasets with cognitive assessment scores actually exist and are accessible.
I started meeting regularly with Joe Xiao, a PhD student who agreed to mentor me on this project, and that alone has changed the pace of things. Having someone to push back on half-formed ideas in real time is worth more than another week of solo reading. This week's conversations helped me go from "healthcare NLP, broadly" to something much more specific.
The idea that stuck was speech. Clinicians already use cognitive assessments that involve some form of verbal or written response — describing a picture, recalling a story, naming objects — and the reason those tasks work as diagnostic tools is that language production is not one skill but many working together: retrieving the right word from memory, organizing ideas into a coherent structure, maintaining a topic instead of drifting off it, and coordinating all of that under whatever executive-function load the task demands. If a disease disrupts any of those underlying processes, it seems plausible that it would leave a trace in how a person talks, potentially before it becomes obvious on a standard test.
Language production draws on several cognitive processes at once — if dementia disrupts one of them, it may show up in ordinary speech.
That is the actual insight that got me excited: dementia assessment today is built around periodic clinician-administered tests — something like the MMSE or MoCA — administered every several months at best. Between those visits, cognitive change is basically invisible to the medical record. It's a series of snapshots, not a trajectory. If we could pull useful signal out of ordinary speech, in principle you could imagine a much higher-frequency, lower-burden way of tracking cognitive change over time. I want to be careful not to oversell this — I have no evidence yet that it's actually feasible — but as a motivation for a project, it's a real gap.
At this stage, I don't have one precise question yet, more like three drafts I'm choosing between:
These aren't the same question. The first is about prediction, the second is about change-detection, and the third is about correlation with an existing clinical measure. I suspect the project will end up closest to a hybrid of (1) and (3) — using coherence-style features to predict a clinical severity score — but I want to sit with the ambiguity a little longer before committing.
Joe pointed out that "speech changes reflect cognitive decline" is a hypothesis with a lot of prior literature behind it already, particularly around discourse coherence — how well ideas connect to each other across an utterance, whether a speaker stays on topic, how much repetition or filler language shows up. I hadn't heard the term "discourse coherence" before this week, so next up is actually reading into what that means formally, since right now I'm using it as a vibe rather than a defined concept.
Read more specifically about discourse coherence in the clinical linguistics literature, rather than general NLP papers, since I suspect the vocabulary and the standard feature sets are already well established there.
I've decided to spend this year trying to do real machine learning research, and I want to write about it as it happens rather than only after the fact. I have no formal research experience — until now, "research" for me has meant reading other people's papers, not writing my own. So the first few weeks of this project have mostly been about learning how research problems get made in the first place, not about writing any code.
The starting point was broad: I know I want to work at the intersection of AI and healthcare, because it feels like one of the few areas where a marginal improvement in a model can translate into something that actually matters to a person's life, not just a benchmark leaderboard. But "AI for healthcare" is not a research question — it's a mood. I needed a way to turn a vague interest into something concrete enough to actually work on.
The most useful thing I've learned so far is a simple framework for decomposing a research idea into four pieces: domain, task, target, and data. The domain is the general area (say, healthcare, or NLP). The task is the type of problem you're solving (classification, generation, retrieval, prediction). The target is the specific outcome variable you're trying to predict or produce. And the data is whatever you actually have access to that connects the domain to the task. It sounds obvious written out like this, but I found that most of my "ideas" were domains without tasks, or tasks without any realistic data source. Having this checklist has already killed a few ideas I was excited about for about a day each.
The framework that turned a vague interest in healthcare AI into something I could actually start testing against real datasets.
I've been doing a lot of reading on Google Scholar, mostly following citation trails backward from survey papers rather than searching keywords blindly, which turns out to be a much better way to figure out what's already been tried. A few candidate directions I looked at this week:
The main lesson this week wasn't technical — it was that a good research topic has to satisfy at least four constraints simultaneously: it has to be technically feasible for someone at my level, there has to be data actually available to work with, there should be some novelty (or at least an underexplored angle on an established problem), and — this one is easy to underweight — I need to actually be interested in it, because this is going to take a long time.
I want to spend more time specifically inside healthcare NLP and see if I can find a task where longitudinal data (repeated measurements over time, not just a single snapshot) is available, since a lot of the medical prediction problems I've read about only use single time points. If disease progression is gradual, a single snapshot seems like the wrong way to study it.