What orthogonal means here
In geometry, two lines are orthogonal when they are at right angles: moving along one does not change your position along the other. In language design the word borrows that idea. A set of features is orthogonal when each one does a single job and its behavior does not depend on which other features are in use.
If a language is orthogonal, you can learn its features one at a time and then combine them with confidence. If it is not, you must also learn a list of exceptions: this works here but not there, this does one thing unless it is next to that.
A simple test
A useful way to check for orthogonality is to ask: “If I can do X, and I can do Y, can I do X inside Y without a new rule?”
Suppose a language lets you use a value in an expression and lets you compute a value with a loop. In an orthogonal design, you can put the loop’s result anywhere a value is expected. In a less orthogonal design, loops might be statements only, so you need a workaround. Each such exception is something the programmer has to remember and something the implementation has to special-case.
Where surprises come from
Surprise in a language is usually the gap between what a programmer expected from the rules they knew and what actually happened. Several common sources are:
- Special cases. A rule applies everywhere except in one situation.
- Hidden interactions. Two features that seem unrelated affect each other.
- Inconsistent defaults. Similar operations handle edge cases differently.
- Silent fallbacks. The language quietly does something instead of reporting a problem.
Orthogonality reduces the first three directly. The fourth is a separate design decision, but it connects to the same goal of predictability.
Examples from odd languages
Esoteric languages are useful here because their rules are short enough to see clearly.
In a stack language such as Befunge, a command that adds two numbers always takes its operands from the top of the program stack and leaves the result there. It does not matter where on the grid the command sits or which direction the pointer is moving. Direction and arithmetic are independent, and that independence is a real form of orthogonality: you can reason about each separately.
Compare that with a feature that couples two ideas. The commands that read and write grid cells let a program change its own instructions as it runs. This is flexible, but it blends the idea of data with the idea of code, and the result is that you can no longer predict what a cell means just by reading the original source. Many people see this as an intended trade rather than a flaw, but it clearly shows how combining two concerns removes predictability. The effects are explored under self-modifying code.
Another design choice is what happens when a command cannot do its job. In Funge-98, an instruction the interpreter does not recognize makes the pointer reflect, turning around rather than crashing. That is a single uniform rule instead of a case-by-case response, so it is easy to remember, though it can hide a mistake. The details are covered in the discussion of reflect behavior. Similarly, popping a value from an empty stack in Befunge is defined to produce zero rather than an error, which keeps the rules simple while making stack underflow harder to notice.
Even the edges of the playfield follow one uniform rule: a pointer that leaves one side comes back from the other, an idea known as a toroidal playfield. Because there is no special “edge of the world” behavior, the programmer has one rule to keep in mind.
Orthogonal does not mean simple to use
It is tempting to equate orthogonality with ease, but they are different. A language can have perfectly independent features and still be painful if there are too few of them to be useful. Some of the most orthogonal languages are the minimalist languages, where each of a handful of commands does exactly one thing, yet writing a program remains laborious.
Orthogonality is about predictability, not convenience. A very predictable language may still require long programs.
Why designers break it anyway
If independence is so valuable, why do languages have exceptions? Usually for good reasons:
- Convenience. A special case can make a common task shorter, such as a shorthand that only applies in one context.
- Performance. A restriction can let an implementation run faster or use less memory.
- History. Features added later must fit around choices already made, and compatibility often wins over cleanliness.
- Human habit. Some notation is familiar to people even though it is irregular.
Each exception is a bet that the benefit outweighs the surprise. Good designers keep the bets few and document them clearly.
Surprise is not always the enemy
In puzzle and esoteric languages, surprise can be the point. A language that behaves in unexpected ways makes programs harder to write, and for people who enjoy that, the difficulty is the entertainment. Languages that are intentionally difficult often break orthogonality on purpose, adding quirks that force the programmer to work around them.
For a language meant for real work, though, the goal is usually the opposite. Surprises cost time, because each one sends a programmer to the documentation or the debugger.
Practical takeaways
When you look at any language, you can ask a few questions to judge how orthogonal it is:
- Do the same rules apply everywhere, or are there regions with their own behavior?
- Can features be nested or combined freely?
- When something goes wrong, is the response consistent?
- How long is the list of exceptions in the documentation?
A short list of exceptions is usually a sign of careful design. It also tells you how much you have to hold in your head to predict what a program will do, which is the practical measure of surprise.