The present and future of coding education

August 28, 2025

At the beginning of my CS teaching career, I taught introductory programming to middle school students at The Lovett School. Among other things, I loved using Scratch, a block-based programming environment in the lineage of Logo. It was a great way to introduce programming concepts in a very engaging way, with instant visual and auditory feedback, while eliminating the issue of syntax errors (typos in code). If I had remained there long enough, I would have adopted Minecraft Education, which has an in-game agent that learners control by writing instructions for it to follow.

Intrinsic motivation is a powerful thing. I believe in giving novice learners the agency to create meaningful things from the very start. The self-motivated learner is the most likely to succeed, so an instructional approach should open the doors to as many of them as possible.

Top-down CS learning

The way I learned to program—several technology lifetimes ago—starts learners at the bottom rung. You learn to manipulate simple values, do math, and print text. You gradually build skills, moving up the ladder into more and more sophisticated concepts: how to create reusable functions that perform the same work on any data they’re given; how to repeat the same series of actions a certain number of times; how to execute certain instructions only when you detect certain conditions. It’s similar to the way we teach math: add and subtract before you multiply and divide. Use unknowns in algebra before calculating rates of change in calculus.

The bottom-up approach works reasonably well for those with a knack for programming and/or the determination to arrive at a point where they can achieve their goals. But it’s less effective for students who have to make do with extrinsic factors, like a course requirement or a career. And, unlike math, programming allows for much more pedagogical flexibility.

A top-down approach to learning to code welcomes all comers into an environment that’s immediately satisfying and motivating. In such environments, students should be able to create interesting, meaningful things much earlier, even if they don’t understand exactly how everything works. Gradually, they learn more about how the code works. Some of them may be satisfied staying at a surface level, tinkering and playing. Others will be motivated to dig deeper—maybe they have a goal that requires more knowledge to achieve; maybe they just want to learn more about how the code works. No matter when a student decides to take the off-ramp, they’ve had a valuable experience because they connected coding to meaning and purpose.

The tension of top-down learning

The challenge when teaching programming from the top down is to give learners enough capability that they can achieve something interesting without overwhelming them. Too much power tends to expose complex systems, and with them the possibility of encountering errors that are beyond the learner’s capability to understand and fix. Too little power can feel restrictive and will fail to engage learners.

The builders of Scratch baked these calculations into their product. Blocks naturally sidestep typographic and simple semantic errors. There are tons of built-in capabilities that give learners the immediate creative satisfaction of painting, animating, making sound, and telling stories. It manages to avoid being overwhelming for beginners, but gives them plenty of room to grow into more complex topics. And it’s open-ended: learners can choose their own path. The Scratch philosophy—”low floor, high ceiling”—perfectly captures their approach.

Most other programming languages and environments require educators to make their own judgments. At Apple, I managed a transition from a bottom-up approach to a top-down one. When I arrived, the Develop in Swift curriculum taught a linear progression from simple concepts to more complex ones. Learners started with basic Swift language concepts before moving into user interface programming, gradually building skills to create ever more complex iOS apps. I designed and implemented an approach to teach SwiftUI app development by starting learners building apps from the very beginning, and gradually exploring more complex UI and programming topics through a progression of small projects.

Swift and SwiftUI tend to be a double-edged sword. It’s relatively easy to read the code of simple apps and understand some of what it’s doing without deeper technical knowledge, but there’s lots of opportunity to lose your way. It was challenging to strike the right balance, but I think we did a pretty good job of giving learners early wins without overloading concepts, and hopefully expanding their knowledge and agency as they finished each tutorial.

The abstraction stack

Part of the challenge of learning to code is that computers are extremely complicated! Over the decades, they’ve grown exponentially in complexity—and the techniques for programming them have likewise grown and matured.

In the early days of binary computers, people wrote machine code—numeric values that represented a series of instructions that make up a computer program. Before long, people were writing assembly language using simple text syntax, which a specialized program translated into binary machine code. That level of abstraction opened up programming to more people and enabled more sophisticated programs. But each computer had its own dialect of assembly language, and those languages were still relatively arcane.

The next layer of abstraction was a language that looked a bit more like English. FORTRAN is a good example. It has instructions like “A = B + 10” to add numbers and assign values to symbolic names. A compiler translated those FORTRAN instructions into assembly language, letting people write programs with a unified syntax independent of the specific assembly language of the computer it was running on.

There are lots of layers above simple languages like early FORTRAN. Each layer hides some of the complexity of the layer below it while offering its own set of abstractions that increase the power and expressivity of a programmer. Ultimately, though, every program eventually reduces to machine code through a series of translations.

Much of computer science education focuses on concepts that bridge one or more of these layers. For example, computers have limited working memory (RAM) in which they do their calculations. Managing that memory—allocating space for new data, removing unused items to avoid filling it up, and organizing the data efficiently—is a very challenging, completely manual exercise using assembly code. In a higher-level language like C++, much of the management is handled for you, but you still have to understand quite a bit about how a computer’s RAM works. Most modern languages add a layer to abstract away manual memory management.

You can write code in a modern language without knowing anything about how a computer manages RAM, and often you’ll be no worse off for it. However, the more complex your code becomes, the more likely you’ll be to run into issues that require knowledge at a lower level of abstraction, even if you never actually write code at that level. Most professional programmers understand—and may work across—several layers. One measure of a developer’s effectiveness at solving problems is their ability to move up and down the abstraction stack as needed.

AI and programming

AI is opening programming to newcomers who have discovered vibe coding with LLMs. A new cohort of hobbyists and tinkerers are discovering how programming can be fun and rewarding—many of whom, otherwise, might have considered it beyond their capabilities.

AI is making a growing impact on professional software development, too: Developers are finding LLMs valuable for automating some of their work. Though the industry is far from consensus on AI’s effectiveness or desirability, the sentiment seems to tilt slightly towards the positive.

However, AI is unlike the other layers of abstraction. When you program in a language like Swift, a stack of tools translates your code in a completely predictable way: from your source code, to Swift Intermediate Language, to LLVM Intermediate Representation, to assembly, to machine code. The same instructions in Swift, processed by tools with consistent settings in the same environment, will always generate identical machine code.

Large language models are nondeterministic—the same prompt is very unlikely to generate the same output twice. How can an AI-assisted programmer guarantee the correctness of a program? Even if it appears to work correctly 99 percent of the time, it’s impossible to know whether LLM-generated code contains hidden flaws unless you can read and understand it.

So programming is still a very valuable skill. Professionals still need to understand how software is built, how it works, and how to manage AI assistants—even if they’re doing the bulk of the actual coding. Even the general public can benefit from a basic understanding of the way computers work, and computational thinking and problem-solving skills will serve them well in any domain.

AI and programming education

Where does that leave the field of programming education, especially at the novice level? I believe that we have to adapt our approach to fit with the current tools and with the habits and aspirations of learners. But we also need to give them a scaffold that they can use to build a solid foundation if they wish to. A base from which to explore programming and computer science in a more structured way.

I think a top-down approach is the best way to accommodate the most novice learners, and I think it dovetails nicely with LLM-assisted coding.

Use-Modify-Create

The Use, Modify, Create framework fits naturally with a top-down approach to learning to code. Learners begin by copying code and learning it by seeing what it does and connecting its output to its syntax. Then they start experimenting with the parts of it that are most accessible to them, developing skills and an innate sense of how things work. As they go deeper, they can begin to bring their own ideas to life.

If you take an iterative approach, you can build a learning progression around this. Start with simple code, build up to a reasonable level of fluency, then repeat the process with a new set of concepts that build upon the existing ones.

Someone who’s vibe coding in the purest sense is relying entirely on an LLM to write code for them, iterating on their work in a chat format. They’re squarely in the Use phase: they have no understanding of the code. Their investment is similarly limited; they’ll run into challenges with complex projects, and without the ability to overcome them, they’ll abandon their work in frustration.

Because of that dynamic, it’s important to limit code complexity when learners are starting out.

To get to the next step, modifying the AI-generated code, learners have to begin to read and understand the output. One way to do that is to make small changes to the code themselves. With the right learning approach, this is a great way to gradually build confidence and investment in the project.

With enough confidence, learners will begin attempting to write code on their own. This is a critical step in learning, and one that they can’t skip before moving to another set of concepts.

I think it’s possible to guide people into more sophisticated programming, and down the abstraction stack, in a way that gives them the best of the top-down instructional approach, using AI to bridge the gap and support creative exploration from the start.

Putting it together

In a recent post, I presented a prototype tool that attempts to solve this problem. I divided the domain of graphical programming into groups of related concepts, ranked them by complexity, and then set up an environment that restricts learners to just the ones they’re currently learning.

When someone starts a new topic—like drawing simple shapes—they’ll chat with the LLM to generate code to draw a picture, and then iterate on that code via chat. As they add features or narrow in on their goals, they should start to read and understand how their code works. Often, asking AI to make very specific modifications won’t quite result in what you want, whereas you can be 100% precise if you know just what to type yourself.

As the learner moves deeper into the modify phase, they’ll gain more ability to craft their artwork exactly as they want it. Eventually, they’ll have the fluency to be able to write entire portions of their code independently. When they feel ready, they can wrap up the topic and move on to one that builds on their skills.

A teacher using this tool could structure a project-based curriculum around it, including their own guidance and assessment techniques. My hope is to give them the best of both worlds: LLM-assisted coding that guides a learner into deep understanding of fundamental programming concepts.

What comes next?

The project is still an early prototype. There are many changes I could make, and I have a long list of ideas for extensions and improvements. But right now I need to test it. I need students and teachers using it to learn and experiment, and I need feedback.

If you teach programming and you’re interested in trying it, please contact me—I’d love to chat with you!

Written entirely without the assistance of AI unless otherwise mentioned, with the exception of extremely light proofreading for grammar and spelling.