How to help a programming student get unstuck
offbyone.us
offbyone.us
The biggest step for me was to never touch the student keyboards. And then I basically only answered their questions with more questions.
Sometimes I would hear students grumble "He never answers my questions...", But in the long run, they would start asking themselves the same questions they knew I would ask (what did you expect it to do?, what did it do?, Why did it do that?)... Then they would usually solve their problem without me!
Or, when they called me over they would start with "I tried a, b, and c" and got results x, y, and z" which is a way better start to a learning experience for everyone than the old "it doesn't work, fix it, please"
I intuitively think one flaw would be that less student would be able to finish the studies, but on the other hand these students wouldn't probably make it in the professional world either.
When you learn gravity, of course you can watch an apple falling from a tree and fogure it out yourself. But you could also watch an apple falling from an tree, try to figure it out for a while and then get a fascinating deep dive in the physics behind it, and then take another dive at the issue.
Just by presenting the issue in the right way instructors can make the difference between people who struggle to grasp something and people who already have the right abstractions in their head but only need to figure out what it means in practise.
The worst peogramming instructors are those who are like: "It was nearly impossible for me to learn this, why do you think it would be easy?"
In any case, this talk claims that studies show giving the answer is the best way to teach (don't shoot the messenger)
The research basically showed that rote learning (the thing so strongly objected to here on HN in many education discussions) is actually effective. But that doesn't contradict the idea that self-guided exploration is bad (paired with an instructor answering questions, even questions with questions), it just shouldn't be where we start or the sole way we teach.
In particular, a common objection to rote teaching programming is often along the lines of, "I taught myself assembly on a Commodore 64, anyone can do it [and I'll imply that it's the best way to learn]." Which can work, and can work for many students. But it's not effective for every student, and creates an artificial barrier (for the majority of students who will learn better, faster, or even at all with a more traditional presentation of the material).
I made it a point that "if it is on an assessment, then they will have seen it in class"
No tricks, no novel algorithms. Just show me that you learned the things that I tried to teach you.
If a lab asks you to write a for loop that prints all the strings in an array, then I've taught the syntax for strings, printing,accessing elements from arrays, getting the length of an array, writing a for loop, etc... There have been hands on activities and/or funsheets for each of those ideas that include fill-in the blank questions where they get the framework of for loops with arrays.
And they get handouts with the syntax / code structures / algorithms.
And they get those worksheets/handouts/etc to reference while working on the lab.
In a classroom environment, it's important to teach and build the foundation to solve problems. But then it's important to make the student do the work to build the connections so they actually learn something. Even if the something they learn is where they can look to find the answer to a question like "does an array start at 0 or 1?"
That's very true, I lost some of the nuance there.
Once I stopped grading the labs, it freed students to experiment and learn rather than rush and stress about the due date. It didn't really matter if they finished the lab, as long as they learned something along the way.
There was, of course, much more to my classes than the labs. Lectures, group activities, "kahoots", discussions, prelabs, funsheets, etc... Lots of ways to assess a students understanding and help catch the ones that are falling behind.
Avoiding getting sucked into touching their keyboards was what I had to do, too.
The idea that a teacher should ever touch a student keyboard in a computer science or programming class to me is absurd.
I agree.
In this case, the students who seemed to work this way were mostly (all?) ESL students, and none of the tutors spoke the native language of the students. So it could be difficult even to indicate what you wanted them to look at, and it was especially frustrating when the errors they were dealing with were trivial syntax errors.
You see articles about not praising kids for being "smart" because that praises something innate about them, but instead praise them for working hard or growing or learning. Sometimes kids that are "smart" in middle school don't have to work/learn/study because everything just makes sense. But then they get to high school and their grades start to dip because their "middle school smarts" don't apply to high school material and they didn't develop the skills to study/learn effectively. But they are known as "smart kids" and smart kids get good grades... So they need to find ways to get good grades... because they are "smart kids"
I hope I was able to help some of those students unlearn some of those habits and develop more educational ways to "get good grades"
Sure, answering those questions with "this is a great opportunity to learn how to read the documentation" is the "right" response.
But I think we've all experienced the frustration of trying to write something in a language we don't use every day, and 'wasting' our time looking up simple things ("okay, what's the exact syntax of a for-loop in this language again?") that just adds friction when we're trying to solve a problem. There's a reason programmers complain so much about being interrupted.
Part of being a good teacher is being able to make those points of friction disappear and enable your students to "get it" and experience the joy of solving a problem.
Don't let perfect be the enemy of good. How many kids would play football/soccer if you were only allowed to play a match with regulation numbers of players on each side, and stopping play every time someone didn't have 'perfect' form?
Did you ever explain why you are doing this? After walking a student through their problem I would praise them and point out they solved the problem themselves. Some people may perceive your style as you being annoyed or unwilling to fully help, so explaining explicitly why you're helping that way would be good.
He was good, but his mysterious guru bit wasn't why he was good, and he should have dropped it.
All this to say, yes, explaining why you're doing things as a teacher is better than just doing them and mystifying people.
Naturally the attitude at that age is "Oh my gosh the teachers love torturing us >:( they hate us >:( school bad >:(". I'm forever grateful for the gifted program at my district for teaching me otherwise. Writing this comment gave me a rush of nostalgia, I think I'm gonna go visit again
Personally, I stopped asking that certain friend for advice after one too many frustrating sessions.
We don't comment on code review with "What do you think this does", we say "Here this is wrong because it will do X and to fix you need Y".
But when it comes to learning, I can't fault the other approach because that is clearly the better way to drill the programming mentality over to the learner. Almost all good engineer knows where to look, and syntactic or logical problems in any context are solved in a very similar way. People who don't have this structured method to approach a bug are going to find trouble in software engineering.
I've had multiple instances with multiple different people that come to me with problems that are pretty much repeats - I've since adapted to "I'll give you hints but you'll need to figure out yourself" in the context of learning.
Obviously, only if the context is learning.
This is the same conclusion I came to as a computer science TA in college. At first, I was naive and tried to teach people the way that I would find it effective to be taught, and I was a bit stymied by this. I slowly started trying different strategies for teaching things, and once I made the (somewhat obvious in hindsight) realization that different students learn in different ways, it became a fun challenge to find a method that worked for a given student.
Some people learn better by doing and discovering, others better by initially imitating and still others in different ways at different times.
For me there are no preferred methods, just a toolkit I build on and pull from as I learn more about teaching.
he figured it out on his own or was at least was able to recall the knowledge from within. if i would have took over his keyboard he wouldn’t have imprinted that as fast, i heard this saying once and it’s encouraging for others to hear/read as well so i’ll say it
“when you are confused, don’t be upset at yourself, it is a good sign. confusion means you thinking about something new or from a different perspective, and that means you are learning”
it’s a humbling experience, i get it, but not a sign you will never understand something, in fact, confusion is the first sign that you are getting it. be encouraged
This reminded me of my old engineering tutorials. Where I went to uni you have these 2/3-on-1 sessions with a tutor to go through a weekly sheet of questions across subjects.
After a short time, we came to the conclusion that the more decorated the tutor, the less good they were at teaching. I had people spanning from just-started PhD students to professors who were members of various academies. The more letters they had after their names, the harder it got to pick up anything from them.
The professors had a couple of things going against them. First, they were no longer warm hands. Too long since they themselves had to analyze transistor circuits. They might know something about how the state of the art is done on an industry grade computer, but no way they could go back to hand drawings of toy systems. They also probably wouldn't know quite how to operate that state-of-the-art system themselves, but trust a postdoc to do it. Second, they were busy. Really really busy, mostly with the business of running a lab. A lot of not-analyzing undergrad level transistor circuits. A lot of grant proposals.
So when you got one of these highly decorated people slumming it with the undergrads, they just wouldn't know what it is you didn't understand. The best were people who'd just done the course not long before you, and understood what to say to trigger you to think the right way. They'd been down the same dead ends, the same oddly worded questions.
This is why I'm no longer so interested in my kids having a lot of professor time. You often hear people complaining their expensive degrees are taught by TAs, but in the end the professor guy would not do much better for the kid.
Things like the difference between single-quoted and double-quoted strings. Special character escapes. Etc...
One person was confused as to why I kept using different kinds of brackets -- round, square, and squiggly.
To me, these basic idioms are so ingrained, that I simply didn't even think to explain them. Of course the squiggly brackets create a nested scope! What's a scope? Uh... that isolates variable names so they don't clash. Why can I refer to the same variables across those squiggly brackets but not those ones? Well... umm...
So you think that's what the language is, a bunch of picky rules.
You need a certain volume of material to see the real picture, and a novice won't have that.
Where in Spanish, once you know the rules, you can read the language out loud without knowing what you're even saying.
Likewise with programming, a language that relies mainly on symbols takes a lot to learn when those concepts don't even exist in your head. What's a function? An expression? A token? An operator? A scope? The best thing to do is use a language that doesn't make you know all those things at once to get started.
That solves the near term schedule risk but does nothing to prevent recurrence.
When I'm managing software engineers I try to make it really really clear that getting stuck is a "known" thing and getting unstuck is a "learned" thing. So to come to me early when they are stuck so we can practice ways to get unstuck. If they wait too long to ask for help, it is a much bigger problem. (and when I am programming for someone I can be just as bad at not asking for help soon enough) for one of my employees who was an ex-marine and really really didn't like asking for help, I got him to think about it as asking someone in their unit to provide covering fire, that is "help" and essential to achieve the objective. They understood that reference really well and we got along great after that.
My point being that everyone is a teacher to somebody, so when an article that frames the teaching as instructor to student, don't make the mistake of thinking it doesn't apply to you. There are things to learn in there.
Do they understand the problem(s) they’re being asked to solve? Toy or otherwise, this strikes me as a necessary condition.
Can they express their solution in discrete steps? Software is brilliantly stupid, and must be told in detail what to do. This step should be independent of language, and is more of a logic exercise.
Can they express their “discrete steps” in the language(s) that are required. This is an engineering problem that can be solved with references, practice, etc. Perhaps we can think of the steps stage as writing in ones native language and the implementation stage as writing in a nonnative tongue.
Debugging. This is a generally applicable skill for any multi step problem. In the case of software there is language and hardware issues in addition to logic and understanding the problem space that usually make things worse. But I have found that if these two aspects can be separated things go much better than blurring the two.
If needed, tuning can be seen as both a (re)design or additional debugging step which is outside the initial discussion. But aside from design, one should separate making something work vs. work fast unless they have a very good grasp of their problem and solution. Much of what we do is separable, as things like abstractions are so useful.
If a student is struggling, the mentor can help them determine which step they are hung up on and the kind of nudging that would be most helpful. People who can create sound, stepwise solutions are useful - they can apply this to pretty much any environment. Knowing the nooks and crannies of a language without good logic/design merely creates hard to maintain code.
Most of it was self-evident information you would naturally pick up, as you made your way through your "programming journey," but the parts about planning were the ones I took copious notes on (and integrated into my own craft).
If you don't know -- exactly -- what problem you're solving, then you're either gonna sit there dumbfounded, unsure of where to go from there, or you'll frenetically start brute-forcing the problem, trying -- sloppily -- everything you can think of.
It never really occurred to me before I read that book that writing the actual software shouldn't be just about 95% of the process. ~40% should be planning: understanding the problem, understanding the tools you have available, and building out a rough outline of how everything's going to work (preferably in pseudocode comments you can then convert into actual code).
Or that you could write code without painstakingly testing every few lines to make sure you "did it right" a la code cowboy.
Wasted a lot of time, and spent too many nights up on caffeine trying to force my way through a problem (albeit it usually worked, with the cost of having to rewrite everything ;).
This technique would eliminate many of the bugs I create (mostly careless mistakes), if I could be bothered to do it, so I'm surprised to see it presented in a negative light.
If they're basic typos/syntax, having something like Rust's "cargo watch" running in a terminal to rebuild files that have changed can be useful. Usually, when I'm done writing a big abstraction, I'll do a quick proof-read, and then go maximize that terminal to see what the compiler found.
A heavy-weight IDE would also serve that purpose. But, I personally cannot stand all the distractions they introduce (drop-downs, red lines, auto-complete shadows, etc.), so I stick with emacs, and just run my unit tests after I'm done writing a big logical block.
If they're matters of logic/constructs/and such: I've found reasoning about in my head the piece I'm writing, first -- and understanding exactly what it's going to do and how it's going to do it, before I even start programming -- to be useful in preventing bad code.
The main problem I've had with "code a few lines, test, code a few more lines, test" is that it always pigeon-holed me into thinking tactically, rather than strategically.
"OK, I gotta write a class that handles X, Y, Z. How's that gonna happen? I don't know. The compiler is a fickle mistress, so I have to focus on getting it to work at all first, before I even think about the grand scheme of things."
When, I could've just written the whole piece in one go, and then dealt with my lexical errors. As long as the logical expressions all make sense, and I understand what my code should be doing, I can make small adjustments here and there.
Dealing with the interpreter/compiler is a distraction, that's best left to a dedicated chunk of time to go over all my errors -- so I can learn from them, and not just give them a quick patch, and move on to the next challenge, without really reflecting on why I had errors.
There's a formal word for this, the Curse of Knowledge. https://en.m.wikipedia.org/wiki/Curse_of_knowledge
All I can tell you is that I learned nothing and just paid to get them done.
Paid whom to get what done?
Maybe I should take some teaching role?
Though I've found it's not so much "remembering" the confusion and mysteries from your own past, but paying very close attention to the student(s) so you notice as soon as there's some divergence in their mental model from what you're trying to relate.
Stop, slow down, address the assumptions you didn't even realise you were making, and it's amazing how much complexity and knowledge you can seemingly transfer into someone else's brain.
Of course they'll forget 2/3 of it over night, but it's easier next time, and once they've "gotten" it (and the corresponding endorphins) once they've got way more motivation to keep going.
Yes! I call it “finding the NePoCU”, Nearest Point of Common Understanding.
Edit: or rather, the technique I use to identify where I can start explaining from.
I've realised immigrants, expats, bilinguals, and just anyone who has lived experience at the interfaces of different cultures are particularly good at this.
When you're both speaking your non-native language in a murky context you 'negotiate' back and forth, using simpler and simpler language (and hand gestures!) until you both agree you're at the NePoCU, and then you slowly build back up and have a great conversation. It's how things like pidgin trading languages emerge.
And of course it's a fun time because you both feel a little ridiculous struggling so dramatically to explain such simple words. Have those conversations in a bar, or over cups of tea, and you'll make some great friends and get invited to 'real' local events :)
After teaching fullstack at a well-known university for a year and tutoring many students I feel that about 90% of them will never be competent entry-level programmers.
There will be much disappointment ahead for the thousands (and thousands) of people being steered towards coding instead of to some more appropriate job (of which, I recognize, many have left the country).
What's crazy is that I feel the majority of people I interview/phone screen would not be a competent entry-level programmer, and they've all had at least 10 years in industry.
The reason I point it out is because if the industry is going to actually get better at interviews (something we generally agree needs improvement) then we have to continuously challenge the assumption that we are actually good at interviewing when it’s our turn on that side of the conversation.
Just want to say, I found this very useful! This is a problem I’ve been trying to solve recently, with only some limited success. (I also want to second the other comment about providing an RSS feed.)
> You’re not a machine. Every teacher and every student is different. The art of teaching is finding a technique that works for the situation at hand.
Just knowing something (like programming) isn't enough to be a good teacher. It really is an art that takes time and caring to develop. Thanks to the author for putting my thoughts into words so well!
My process seems to boil down to not realizing I was supposed to get frustrated and give up.
Even then, it's a set of heuristics and guidelines. Neither the book nor my comment claim it to be truly universal.
The idea is that one may induce a path to solution in a student by stripping a problem down to it's knowns, unknowns, and constraints so that it may be comprehensible, then connecting these elements back together by asking pointed questions that could have conceivably occurred to the student. He gives plenty of practical examples of how one may dial the complexity of these questions up and down, how to relate by analogy, and plenty of other techniques for approaching problem solving generally all in a flimsy paperback that can be finished in two evenings in a weekend.
The most impressive part, to me, is that nearly every example is both illustrative enough to be a word-for-word guidebook for math tutors, and accessible enough for even a middle school student studying Euclid to understand in it's entirety.
More helpful is backing up and framing the problem to be less abstract. When learning to code, we're learning new ways to represent things that we likely already intuitively know. In that way, you're teaching expression. The two are taught very differently, imo.
For example consider a unification algorithm (e.g. https://reasoning.page/29021/unification-sort-of-in-rust which is an explanation of it that I wrote). The individual parts of it do not seem that spectacular, and also seem quite abstract. It is so simple (in my mind) that it could not possibly be correct. However, there is an emergent property when the interactions between the different parts of the program are taken together.
The simple best way to program is exposure, and a lot of it. Not these boring petty tasks we do. They're logic puzzles and not much else. Showing someone how everything works from start to finish in creating a website with a specified framework helps tremendously. Or how to work with low level C programming making practical applications.
For myself personally I developed a method of TODO's with some of what my lead taught me mixed in. My lead doesn't like redundant code, but said "working code is better than no code." So what I did is made one type of TODO that means "this code sucks or should very much be revised." Another one I have is "talk to my lead." With those, I don't address it with my lead unless we already have a meeting and it'll be quick, or I compound enough of them to devote an actual meeting regarding them.
Overall it has helped me stay organized, and it keeps me from annoying my lead. So what I'd add is, a system for people to make their own notetaking is vital when learning. If you don't have that or arent willing to take the initiative to do that, personally I think coding is not for you.
> I think what a lot of instructors fail at is they don't give students a working example of how something works. Sure you're not really going to learn, but what's the point of me having a half-assed assignment that isn't useful for me referencing a couple years down the road?
Like everything else in life it's a balancing act (or a chicken and egg problem). In a university/college context in particular it's not about being practical, it's about learning the (hopefully up-to-date) theory. There are concepts, classes of problems, and mental tools in computer science that are extremely valuable, but if you don't teach them explicitly you have everyone reinventing the wheel, and worse, doing it without a common vocabulary.
But as a student you can learn/memorise some concept (let's say recursion as an example), but you never really 'get it' until you've struggled with a real-world problem and realised that recursion solves it elegantly.
Sometimes the way we teach reminds me of hypnopaedia/sleep-teaching in Brave New World:
``` A small boy, asleep on his right side, the right arm stuck out, the right hand hanging limply over the edge of the bed. Through a round grating in the side of a box a voice speaks softly.
'The Nile is the longest river in Africa and the second in the length of all rivers of the globe. Although falling short of the length...'
At breakfast the next morning, 'Tommy,' someone says, 'do you know which is the longest river in Africa?' A shaking of the head. 'But don't you remember something that begins: 'The Nile is the...'
'The-Nile-is-the-longest-river-in-Africa-and-the-second-in-length-of-all-the-rivers-of-the-globe...' The words come rushing out. 'Although-falling-short-of...'
'Well now, which is the longest river in Africa?'
The eyes are blank. 'I don't know.' ```
The takeaway (in the book at least) is that rote-learning like that has no value because its not conscious knowledge (but it's great for _moral_ lessons, like 'Ending is better than mending'). But I've lost count of the times where I've come up against a problem that niggles at the mind and realised I learned to solve it years ago at university, and it all falls into place. If nothing else, you know what to google for.
So I've come around full circle on how theory-heavy universities are (if we don't teach theory at university, where would we teach it?), but agree that there's a huge benefit to student experience by pairing it with exposure to full systems.
The older I get the more I find myself regretting (/resenting) how we've off-loaded so much job training to universities. You know how students can get exposed to real-world systems and learn real-world lessons? By having them recruited by real-world companies who invest in their development.
Seems like a CS degree would be most useful after you've been working with real-world code for ~5 years. Perhaps we need to split out most of the content of the current undergrad degrees into a CS equivalent of an MBA? Let the undergrads focus on practicality (ie, making the MIT missing semester the core experience), then come back in a few years to learn all the "correct terms" for the problems you've been coming across and learn the best-practice/optimal approaches.
TODO: --0-- This is my "talk to my lead" todo. If i can't figure something out, or i need clarification on something, i throw this on here. Sometimes I'm able to figure it out later which is why i mark these. But normally they're reserved for further clarification and I'm just going on assumptions until I need that info.
TODO: --1-- This todo is a task that needs to get done or there is something vital and I haven't implemented it or need to figure it out.
TODO: --2-- This todo is the redundant or bad code todo. It works and is like a priority 1, but only when it prevents you from completing another priority 1.
TODO: --3-- This is a "it's not great and I'd like to redo this, but it gets the job done" level. This one was for when i'd have something petty or minor that i know is maybe bad form or inefficient, but i just don't have the time to deploy and test a complex datastructure to it. So what if I have 4 nested for loops. It works and it's reliable and thats all the client needs.
TODO: --4-- This is my wishlist one. Typically I do this more for personal projects, but on occasion at work, I know theres a feature i could implement at some point that could really help out. This usually falls in the category of "if i have absolutely nothing to do." It's just a way to keep ideas somewhere.
I've started to having global todos but I haven't fleshed it out much. Typically i'd throw them at the top of a class or something stating an issue or bug. Haven't really messed with it much but my tiering system has helped tremendously.