All Posts
Engineering Philosophy · Part 2 of 4
15 March 2026 · 6 min read

The Teacher and the Doer

I started code reviewing for a junior developer a few months ago. It taught me more about my own understanding than I expected, and revealed a model of learning that changes how I think about software knowledge.

A few months ago, I started code reviewing for a junior developer on our team. I expected it to be straightforward: read the code, spot the issues, talk them through on a call. I'd been writing production software for a while. How hard could it be to explain what I already knew?

It turned out to be one of the most revealing experiences of my career so far. Not because the code was difficult, but because the act of teaching exposed gaps in my own understanding that I didn't know existed.

The Gap Between Doing and Explaining

There's a specific moment that stays with me. I was reviewing some code where the junior had written a service that mixed data fetching with business logic. I knew instinctively that this was wrong. I could feel it. If I'd been writing the code myself, I would have separated those concerns without thinking. It was automatic.

But when we got on a call to discuss it, I froze. "Because it's better" isn't feedback. "Because separation of concerns" is just naming the principle without explaining it. I needed to articulate the specific reason this separation mattered in this context: what would go wrong if we didn't do it, what it would cost us later, what it would make easier.

That moment taught me something important: there's a difference between being able to apply a rule and being able to explain why the rule exists.

The stages of understanding
The stages of understanding

Three Stages of Understanding

Through the experience of code reviewing, and many conversations with my mentor about this exact problem, I've come to think about software knowledge as moving through three stages.

Stage one: pattern recognition through instances. You see your mentor or a senior developer do something a certain way. Then you see them do it again in a different context. And again. Over time, your brain starts to recognise the pattern without anyone explicitly stating the rule. This is how my mentor taught me: not by lecturing about SOLID principles, but by showing me specific instances of applying them in real code. The learning was implicit. Here's how I structured this service, here's why I split this module, here's what I changed in this piece of code.

Stage two: unconscious competence. After enough instances, you can apply the pattern yourself. You write code that separates concerns, that keeps functions small, that names things well... but if someone asks you why, you struggle to articulate it beyond "it feels right" or "that's how it should be done." You've internalised the rule, but you can't externalise it. This is where most competent developers sit for years, and it's a perfectly functional place to be.

Stage three: teachable understanding. This is where you can not only apply the rule, but explain the principle behind it, describe the contexts where it does and doesn't apply, and generate new instances that illustrate it. You've moved from knowing-how to knowing-that. You can rationalise it, defend it, and critically, know when to break it.

The jump from stage two to stage three is what code reviewing forced on me.

The Feedback Loop

Here's where I originally had a simpler model in my head: you do first, then you read to understand what you did. Practice, then theory. But I've come to think that's too linear.

What actually happens is more like a feedback loop. You see instances and develop tacit knowledge. Then you read something, an article about dependency injection or a chapter on domain-driven design, and it clicks because you've already felt the problem it solves. That reading reshapes how you see the next instance. You apply the refined understanding, encounter a new edge case, go back to reading with a sharper question, and the cycle continues.

Reading without doing produces cargo cult understanding. You can recite the principles but you've never felt the pain they address. You know that "you should favour composition over inheritance" but you've never been burned by a deep inheritance hierarchy that made a simple change cascade through twelve files.

Doing without reading produces superstition. You know that something works, but you might attribute it to the wrong cause. You always write small functions because a senior once told you to, but you think it's about readability when it's actually about testability. The practice is correct but the mental model is wrong; wrong mental models eventually lead you to apply the rule in contexts where it doesn't help, or fail to apply it in contexts where it would.

The strongest developers I've observed alternate between the two rapidly. They try something, read about why it worked, try a variation, read a different perspective, and so on. The theory and the practice aren't sequential; they're interleaved, each one sharpening the other.

What Code Reviewing Taught the Reviewer

The irony is that reviewing code for a junior developer pushed me from stage two to stage three on several concepts I thought I already understood.

When I had to explain why we inject dependencies rather than instantiate them directly, I realised my own understanding of dependency injection was more mechanical than principled. I knew the pattern, but articulating the specific benefit (that it makes the dependency relationship explicit and the component testable in isolation) required me to think about it more carefully than I ever had when just writing the code.

When I had to explain why a certain function should be extracted, I couldn't just say "it's too long." I had to identify the specific reason: this function is doing two things with different rates of change, and when one changes, the other shouldn't have to.

Each of these explanations forced a precision of thought that writing code alone never demanded. The junior's questions were the best forcing function I'd encountered. Not because they were sophisticated, but because they were honest. "Why?" is the most powerful question in software development, and it's the one we stop asking once we reach unconscious competence.

The Implication for How We Teach

This model has practical consequences for how we should structure learning in software teams.

Don't start with the textbook. If someone hasn't felt the pain of tightly coupled code, explaining the dependency inversion principle is just noise. It'll sound theoretically correct and practically meaningless. Instead, let them write the tightly coupled code, let them experience the change that cascades everywhere, and then show them the principle. The learning sticks because it has a hook to attach to.

Don't stop at the doing, either. A team that only learns through osmosis, watching seniors and picking up habits, will develop capable practitioners who can't explain their decisions. That's fine until they need to make a decision in unfamiliar territory, where there's no pattern to match against. That's when the rationalised understanding matters.

Create opportunities for stage-three learning. Code reviews are the obvious one, but there are others: pair programming where the more experienced person narrates their thinking, architecture decision records where you have to write down why you chose an approach, and team discussions where practices are questioned rather than assumed.

The Test

Here's a test I now apply to myself: for any practice I follow, can I explain not just what I do, but why I do it, and when I would stop doing it?

If I can only say what ("I write unit tests") then I'm at stage one. If I can say why ("because they let me refactor with confidence") then I'm getting to stage three. And if I can say when I'd stop ("when the cost of maintaining the tests exceeds the confidence they provide, which happens with highly volatile UI code") then I'm there.

The junior developer doesn't know it, but their code reviews have been a great learning experience for me. Not because they taught me new techniques, but because they forced me to understand the ones I already had.

Engineering Philosophy · Part 2 of 4