The same artifact, two completely different questions
When a senior developer reviews a pull request, they are asking one question: should this code merge? They check for correctness, edge cases, security implications, and whether the code fits the patterns used across the codebase. If the code is wrong or risky, the review blocks the merge and explains why. This is exactly what code review should do.
PR grading for learning asks a different question: what concept does this developer not understand yet? The artifact is the same pull request diff. But the goal is not merge-readiness. The goal is a learning signal. The two outputs look similar on the surface but serve completely different purposes, and conflating them produces bad results for both.
We built HackQuest around this distinction. It changed most of our design decisions.
What code review optimizes for
A good code reviewer is optimizing for the health of the codebase and the correctness of the change. They are not a tutor. Their job is not to explain every concept the author might be missing. In fact, doing so would make code review unusably slow. A comment like "you used a mutex here but this would be cleaner with a channel because of how Go models concurrency" is a nice-to-have. A comment like "this will deadlock under concurrent writes" is the actual job.
Code review also operates under a social contract. The reviewer and the author are colleagues. The feedback is calibrated for someone who is already expected to understand the codebase and the language. Reviews assume competence. They point out specific bugs, not underlying concept gaps. This is appropriate. A production PR is not a classroom exercise.
The result is that code review is excellent at catching bugs but noisy as a learning instrument. A reviewer who finds a goroutine leak will say "this goroutine will never exit if the channel is closed early." They will not explain channel ownership semantics from scratch. They should not have to. But for a developer who is still building their Go mental model, that explanation is exactly what they need.
What PR grading for learning optimizes for
When we grade a PR for learning purposes, we are asking a different set of questions. Not "is this code correct?" but "which concept is wrong here, and is this the first time this developer has made this kind of error, or the third?"
The concept layer is the critical difference. Most bugs in a stack-switching context are not random errors. They cluster around specific conceptual mismatches. A Python developer switching to Go will frequently write incorrect error handling in the first few weeks, not because they are careless but because error-as-value requires a different mental model than exception-raising. The surface symptoms vary: forgot to check err, swallowed an error silently, returned an error from the wrong callsite. But they all point to the same root concept.
A learning-focused grader identifies the root concept, not just the surface bug. It then asks whether this concept has appeared in previous PRs from the same developer. If it has, the lesson priority rises. If the developer handled it correctly two PRs ago but is getting it wrong again now, that is a different signal than someone encountering it for the first time.
This recurrence tracking is the part that is structurally impossible in normal code review. A reviewer has no systematic memory of what conceptual gaps they have observed in previous PRs from the same author. They might remember informally if the author consistently misuses error wrapping, but they are not maintaining a running model of the learner's knowledge state. They have no reason to. That is not their job.
The same diff, different output
The practical difference shows up when you compare the output of a code reviewer versus a PR grader given the same diff.
Consider a Go PR where a developer has written:
go func() {
for msg := range ch {
process(msg)
}
}()
The channel ch is passed in from a caller that may return early without closing it. A code reviewer focused on correctness would flag this as a potential goroutine leak and likely add a comment about passing a context for cancellation. They would probably also link to the relevant pattern or a similar example in the codebase.
A PR grader for learning would note the same issue, but frame it differently. The underlying concept is channel ownership: who is responsible for closing this channel, and what happens to the goroutine if nobody does? The grader would connect this to a lesson on goroutine lifecycle management and flag it with a severity indicating whether this is a new concept for the developer or a recurring gap. If the developer made a similar error in their previous PR, perhaps closing a channel in the wrong goroutine, the lesson priority would increase.
The comments in the diff might look similar, but one is optimizing for "this PR can now safely merge" and the other is optimizing for "this developer now understands channel ownership at a deeper level than they did yesterday."
Why recurrence tracking changes everything
The most valuable signal in learning-focused grading is not finding a bug. It is finding the same class of bug across multiple PRs, slightly differently expressed each time.
A developer might write five PRs over three weeks, all of which contain some variant of a context propagation error. In each PR, the specific symptom is different: one has a goroutine that ignores cancellation, one has a database query that does not accept a context, one passes context.Background() where a derived context should propagate. A code reviewer looking at any single PR might leave a correct comment on that specific issue. But only a system maintaining a concept-level model of the developer's knowledge can see that all five errors share the same root: the developer has not yet internalized the Go convention that context flows through the call chain as a first argument.
Once you identify that pattern, the next lesson almost selects itself. The lesson is not "here is how to use context.WithCancel" because the developer already knows the API. The lesson is "here is the ownership model that explains why context propagates the way it does, and what breaks when it does not."
A distinction worth keeping
We are not saying code review is bad for learning. It is often excellent, especially when a thoughtful senior developer has time to explain the reasoning behind their comments. The problem is that this kind of deep explanatory feedback is rare in practice. Senior developers are busy, review queues are long, and detailed pedagogical explanations are not what most review comments are for.
PR grading for learning is not a replacement for having a knowledgeable colleague who can talk through a problem with you. It is a way to get consistent concept-level feedback on every PR you submit, without relying on someone else's availability. The two can coexist. In fact, graded feedback tends to make human code review more productive: if a developer already understands why their error handling pattern was wrong, the human reviewer spends less time on fundamentals and more time on design-level judgment that automated grading cannot provide.
The distinction matters for how we design HackQuest. We built a learning tool, not a code review assistant. Every design decision traces back to that: what does this developer not know, and how does that change what we show them next.