By Kim Merrill, Principal Computer Science Content Creator

Computer science education has a mastery problem.
Students pass courses. They complete assignments. But put them in front of a slightly unfamiliar problem and many stall out.
We tend to treat this as an individual failure. They’re not trying. They’re not curious. They’re “just not cut out for this.” But what if the problem isn’t the students?
What if it’s the design of the curriculum itself?
How computer science curriculum evolved
The way we teach computer science today didn’t start from a single, coherent theory of how people learn to program. It evolved over time.
With each shift, the goal was to solve a real problem. And often, it did. But every solution came with a trade-off, and over time, those trade-offs accumulated.
What we’ve inherited isn’t a system designed from first principles; we’ve inherited a system that reflects its history.
Origins in mathematical theory
Computer science didn’t begin as its own discipline. It emerged from mathematics. So when universities began formalizing CS programs, they housed them inside math departments. And that decision had consequences.
Early curricula emphasized discrete math, logic, and proofs because it aligned with how the field understood knowledge. Programming was treated as secondary and more clerical than intellectual. It was assumed to be something students would pick up on their own.
Those who developed practical fluency did so independently, creating the impression that the system worked.
Hey kids, programming is fun!
At the turn of the century, industry demand for programmers and a push to broaden participation drove a shift toward engagement. Curricula increasingly centered on motivating contexts like animation, games, and web development to make CS feel immediate and approachable.
If the application was fun, we assumed students would learn by doing. So we placed less emphasis on explicit instruction. This expanded access, but it also made learning fragile. Students could make an animation, but take away the graphics library and the underlying model disappeared with it.
The hope was that a positive first experience would motivate students to persist through a theory-heavy undergraduate degree, where we assumed deeper understanding would follow. Mastery could wait.
Training digital citizens
As technology became ubiquitous in students’ lives, the goals of CS education expanded. Parents, policymakers, and educators pushed to broaden the scope to include digital literacy—topics like data privacy, impacts of computing, and the internet.
Introductory courses grew into broad surveys of computing, with programming relegated to just one unit among many. Unsurprisingly, this left little room for mastery to develop.
A patchwork of learning goals
This isn’t to say these evolutions were failures. The hard work of many CS educators has successfully lowered the barrier to entry and cultivated a more inclusive field.
In fact, I’ve seen these curricula work, for myself and in my classrooms. But they tend to work for the same students: those with the motivation to go beyond what’s assigned.
The system relies on students to fill in the gaps. We expose them to concepts but don’t structure the practice they need to turn these learned concepts into durable skills.
We leave mastery to chance.
Why programming should be the foundation
If we want students to leave a course with skills that transfer, we can’t treat computer science as a broad umbrella. We have to choose what matters most.
For an introductory course, that choice has to be programming.
A student who can read, write, and reason code can apply that same thinking moving forward, whether they go on to study theory, build systems, or simply consume technology. In that sense, programming isn’t in opposition to any of these; it’s the foundation for them.
But programming isn’t just a body of knowledge. It’s a skill. And like any skill, it takes time and repeated practice to develop.
That creates a constraint. If programming is the foundation, it can’t be something we rush through on the way to everything else. We have to narrow the scope enough to give students time to actually build fluency.
What mastery learning looks like in CS
Take a concept like loops.
In a typical course, we introduce loops once: students learn the syntax, complete a few exercises, and move on. By the end, students can recognize loops, but they can’t reliably apply them.
In a mastery-based course, loops aren’t an isolated lesson. They’re a set of skills students revisit across contexts, with gradually less support.
Reasoning before syntax
Students don’t start with syntax. They start with a problem: repeating the same steps over and over. That friction gives them a reason to care about the concept before they ever see a loop.
From there, students begin by reading and tracing code. What does this loop do? How many times does it run? What changes each time? The goal isn’t to write code yet; it’s to build a mental model of how iteration actually works.
Application in context
Then, students modify existing code. Change the range. Adjust the condition. Add a counter. We give them the basic structure, but they have to make the decisions.
Once they understand how to adapt the pattern, we combine it with other skills: variables, conditionals, data structures. Students write programs that count, filter, or accumulate values. The problems change, but the concept stays the same.
Finally, students are asked to solve problems without being told to use a loop. Now the question isn’t whether they can write a loop, but whether they know when and how to use one.
That’s the difference between familiarity and mastery.
How Khan Academy designs for mastery
So what does it look like when you actually build a course this way?
That’s what we’ve been designing for our Intro to CS – Python course.
Structured practice
For each concept, students begin by tracing code in Exercises. They move to scaffolded practice, with immediate feedback in Challenges. Eventually, they build their own solutions in Projects. Each stage builds on the previous one, gradually shifting responsibility from the curriculum to the student.

We don’t assume students will figure out the programming process through trial and error. We teach it explicitly. We trace code step by step, practice debugging, and study worked examples that move from problem to solution. The goal isn’t just to write code; it’s to understand how to approach problems.
Real-world problems
We also go beyond construction to evaluation. Students compare different approaches during code review, identify test cases to validate their programs, and reflect on their work. Throughout this process, they consider readability, assumptions, and possible extensions.
Engagement comes from authentic contexts, not just surface-level themes. Students explore simulations by modeling an infectious disease, data privacy by building an ad-targeting system, and encryption by designing their own cipher.As they move through the course, they revisit core concepts in different contexts until those ideas become tools they can apply independently.
Designing for understanding
A CS curriculum grounded in mastery learning means designing for understanding, not just exposure.
We focus on what matters most and let go of what doesn’t. We teach students to think with code, not just produce it. And we leave time for structured, repeated practice so ideas stick.
Because learning shouldn’t be something we assume. It should be something we design for.
You can explore our approach in action by checking out Khan Academy’s Intro to CS – Python course.



