The Grammar of Operations

If Duolingo is so useful for learning foreign languages, why isn’t there a Duolingo for programming?

Vocabulary vs. Grammar

When learning a foreign language, we often start with vocabulary: learning the words for common objects, actions, and concepts. Then we learn the grammar, usually by combining vocabulary into simple phrases, from which we infer the rules for combining words into phrases and sentences, and categorise the various kinds of words and phrases.

But when learning programming, we often start with the syntax: the rules for writing valid code. That’s because when we learn a language, we are usually learning it from a human. They may laugh at us for getting grammar wrong, but they can also provide feedback and gentle correction. When learning programming, if we get the syntax wrong, the code won’t run at all. The grammar must be correct or the interpreter cannot even get started running our code. This is so important that syntax errors are recognized as their own category of errors.

Once we learn the grammar perfectly, then the vocabulary starts to matter. We need to distinguish keywords, which are words with special meaning in the language, from identifiers, which are names we give to variables and functions. We need to learn the different data types, and how to convert between them. We need to learn the operators for performing different operations on data. And finally we learn the standard library functions out of which we will construct the rest of the program.

Very few people enjoy learning grammar first, and even fewer enjoy having to get their grammar perfect before picking up vocabulary. But that’s what programming is like. In short, programming is like learning a foreign language with a very strict grammar and zero tolerance for mistakes.

Awareness vs. Fluency

Writing our first successful program is an immense milestone. Seeing output appearing on screen as we expect—in black text and not the familiar red of an error message—is a moment of triumph. But it does not make writing the next program any easier. Each new program is a new challenge, requiring different patterns of thought and problem-solving skills, and resulting in a different solution. Furthermore, lacking touch-typing skills adds a significant hurdle to acquiring fluency in programming—imagine if you could only speak one syllable at a time!

When we first learn a foreign language, we are aware of the grammar and vocabulary, but we are not yet fluent. We can understand simple sentences, but we cannot yet think in the language. We have to translate from our native language in our heads, then construct sentences word by word. This is slow and laborious, and we make many mistakes along the way.

In our head, we might recognize some errors and problem patterns, and know that we have solved similar problems before. But our fingers do not yet have the muscle memory to translate our thoughts into code. We are aware of the concepts, but not yet fluent in their application. Building that fluency, that fingerspitzengefühl (“fingertip feeling”), takes hundreds of hours solving different types of problems with code. Without that fluency, we cannot “think in code”; our knowledge of programming does not yet translate into the subconscious ease of a native speaker of a language.

Like with sports, that will require a couple hundred more hours of practice and many more problem types to achieve. In short, in programming we need to “get our reps in” before we can achieve fluency in tackling various kinds of problems.

Shared meaning

When we converse with another person, the act of meaning-making is shared; we each bring our own experiences, contexts, and interpretations to the conversation. We can clarify misunderstandings, ask for elaboration, and adjust our language to suit the listener. Meaning is not only created by the speaker, it is also shaped and influenced by the listener.

But when we write code, we don’t have another mind reflecting our meaning back to us. The code interpreter is not a person, it is a machine that follows strict rules. It cannot ask for clarification, it cannot adjust its understanding based on context, it cannot infer meaning from tone or body language—unless we paste the code and error message into ChatGPT. The entire burden of meaning-making falls on the programmer.

This can feel lonely and isolating. Many beginner programmers do find it helpful to have a mentor or a pair programmer to share the burden of meaning-making. But ultimately, the act of writing code is a solitary one, requiring the programmer to take full responsibility for the clarity and correctness of their code. In short, the largely solitary nature of programming means that it presents a heavier burden than the learning of a spoken language, which is inherently social.

Conclusion

Learning to program is like learning a foreign language with a very strict grammar and zero tolerance for mistakes. It requires mastering the grammar before vocabulary, and achieving fluency through hundreds of hours of practice. The solitary nature of programming means that the burden of meaning-making falls entirely on the programmer, making it a challenging but rewarding endeavor.