Questions, answers, and reprexes

A “CSS Battle” YouTube video made me ponder metacognition in programming. I had questions, reprex gave me answers.
Teaching
Learning
CSS
Programming
reprex
Author

Garrick Aden-Buie

Published

March 16, 2021

I stumbled into a niche YouTube genre: Web Development online instructors (a.k.a content creators) challenge each other to “CSS Battles”.

Turns out they are fascinating videos where experienced programmers talk and fumble their way through their coding.

As they talked through their thought processes, I thought about the questions we ask when our code goes wrong and the answers we get when we reprex.

CSS Battle with The CSS King

I challenged The CSS King to a CSS Battle on YouTube

I’ve thought a lot about this video since I watched it. Kyle Cook and Kevin Powell both make great YouTube videos teaching CSS and front-end web development concepts.

In this video, they simultaneously tackle three CSS challenges with a time limit of 10 minutes for each. Both Kyle and Kevin talk through their process and the video switches between the two of them as they build up their solutions.

Writing code is testing your theory of the solution

This is the part I loved: beyond confirming that writing CSS involves a lot of let’s just try width: 100% and I guess it should be 90px… 85px… 75px…, it was awesome to see how much of solving a complicated programming problem is about having a vague idea of the structure of a final solution and working toward that goal with many iterations with tiny, confirmatory tests.

CSS Battle Christmas tree challenge: three stacked triangles

In the first challenge, they made a cute little CSS Christmas tree. There are some tricks in here that you just need to know — for example, you can make triangles with CSS using a trick with borders — but neither Kyle nor Kevin start out knowing the exact final answer. Kevin narrates:

I have to remember how to do an up arrow… Let’s just try… Is it like this? … Ah! I did get it right! Awesome, awesome. I don’t use this trick too often and I always forget how it works.

Two roads diverged in a wood, and I…

Kyle starts off with the border trick, and actually explains how it works in detail, but pretty quickly his solution goes a little off the rails. This is where I found myself fascinated.

At this point, both programmers are on the right track. The video flips back-and-forth between the two and you can see that Kyle has the right idea. But he starts second guessing his approach. He questions whether or not he’s using the right trick and he almost starts over from scratch.

Where it seemed like both programmers were headed for the same solution, Kyle’s questioning leads him to reverse course and try a radically different approach. Kevin, on the other hand, keeps on pushing forward. A small difference in the path chosen leads to two very different outcomes. In the end Kyle ends up coding in circles, trying to resolve problems that arose because of the circuitous path he chose when he gave up on his original direction.

Writing code is thinking about code

In my experience, writing actual code is an improbably small part of programming. I love the feeling of flow when I connect with a problem, when I’m mostly certain about how I’m going to solve it, when I can just write code — and not boiler plate, I mean real code. But day-to-day, I think about, read about, muse about, wonder about, and puzzle over much more code than I actually write.

ionicons-v5-j

Sidenote: Pair Programming

As a side note, I find that the flow is easier to find when I’m pair programming. It seems paradoxical, but having another set of eyes and all the extra cycles of the other person watching, always helps me stay on track whenever I would normally get lost in the frustration of a diversion.

Pre-pandemic, I always found it hard to convince others to join in pair-programming. But video conferencing makes it easier than ever to look over someone shoulder while they’re programming. It also makes it easier to switch roles while pairing, especially when you’re the navigator (the one watching/guiding) and need to step in to the driver role — here, let me control your screen for a minute so I can show you…

Questioning your questions

The art of question asking while programming is often about understanding where to put your mental break points. Not when to take a break from staring at your screen (that’s also an important skill, especially when you start flailing1), but points at which you pause and evaluate whether the code in front of you is doing what you think it should be doing. In your binary search of the problem space — will my broken code work if I poke this over here — choosing where to draw the boxes around your code is a skill that comes with time and experience.

Two hands, one holding a pencil and the other holding a right-angled ruler, preparing to draw a line.
Photo by Karolina Grabowska

Sometimes, it’s made harder by experience. Is this bug I’m working on broken because of a local mistake, or is it broken because I’ve installed the latest version of three of this package’s dependencies from GitHub? Is it because I’m using the latest preview version of my IDE? Or is it because I was working on another feature for a package called by this package and I’ve installed my locally dev version of that package and this package is unhappy about that?

With experience I’ve gotten better at looking at a problem, drawing a dotted line around a section of code, and finding a way to test whether the bug is inside or outside that line. But I can just as easily get distracted by a false positive and spend too much time testing things that are totally unrelated to the actual problem that I was trying to fix.

One of the hardest parts of learning how to program is learning about how to draw these invisible lines. When everything is new, it’s hard to know where to look when something doesn’t work. It’s easy to think of questions that lead you further from the truth or that lead to non-local jumps in logic: rather than differentiating between two possibilities A or B, a beginner will jump from A doesn’t work so the problem must be possibility K.

One of the biggest differentiators of experienced programmers from novices is their ability for metacognitive programming. They don’t just think about code in the sense of remembering a few magical commands, but they have a self-awareness of how they think about code. With experience, programmers move more quickly from what if I try this to what could be going on here and how can I test it?

Not that we don’t resort or fall into flailing. I can’t tell you how many times I get in a fight with my code just before lunch, while having the awareness to say to myself this will be easier if I take a break and eat some food, only to hear the voice on the other shoulder shout keep going, you’re so close, just try this one last thing.

A reprex is the answer

How do you learn and practice the critical skill of asking your code questions? With a reprex.

A reprex is a small, reproducible example. Typically, it’s a small little bit of code with a tiny bug. It’s reproducible, meaning that it’s designed to work anywhere for anyone, or at least to work with the minimal number of dependencies. It doesn’t have to be buggy code, it can just be a tiny example.

And it’s a very powerful technique.

On the one hand, a reproducible example makes it easier to get help by making it easier for people to help you. For a great introduction to reprexes from this angle, you should definitely watch Sharla Gelfand’s talk make a reprex… please.

On the other hand, when you create a reprex you’re practicing the art and science of asking questions of your code. At every iteration you ask yourself: is this part here essential? Is this package really required? Do I need this much data to make my example reproducible, or can I get to the same place with a smaller data set? Helpfully, you get immediate feedback: if you take out too much, your example doesn’t do the thing anymore. This skill of editing and question asking is critical — and it’s why the process of creating a reprex so often leads to a discovery of a solution.

You don’t always have to start with a large problem. It can be just as valuable to create a toy problem to play with as you’re learning a new concept or a programming method. It’s easier to tinker and explore when it’s small, just like it’s easier to be confident you understand what the code or method you’re learning are actually doing when that’s the focus of your code.

So, the next time you’re flailing, stop and make a reprex. The next time you’re learning a new concept, stop and make a reprex. And the next time you’re teaching beginners how to program, feel free to teach them how to stop and make a reprex2.

Footnotes

  1. flailing is a technical term. If you’re trying many things but you don’t have the feeling of getting anywhere, then you’re flailing. You’ll know you aren’t flailing any more when you have a feeling of getting somewhere even when your ideas aren’t panning out — because they’re at least telling you what won’t work.↩︎

  2. Seriously, teach reprex early. You don’t have to get into the weeds about environments or file paths or other technical details. Learning to program is learning how to understand code; they’ll either learn how to approach problematic code in a systematic way with a reprex, or they’ll learn how to debug their own code in the wild with fewer guard rails and tools.↩︎