The Gap Between Ideal and Reality in Algorithm Learning

Algorithms, along with data structures, are ubiquitous in the world of computing: from a simple function call to routing and load balancing in computer networks. From a sufficiently theoretical perspective, an algorithm is a logical model, and the specific code is merely a translation in a particular programming language. Some more radical discussions might use assembly as a unified benchmark to minimize implementation noise as much as possible, but it is more common to express algorithms using pseudocode—sequence, loops, and conditionals are already powerful enough.

Our general impression of algorithms is centered on asymptotic complexity analysis, especially under the RAM model (Random Access Machine model). This simple yet powerful model has effectively guided algorithm design for decades, remained robust in the multi-core era after the 2010s, and is still one of the core tasks of undergraduate education today. However, a purely formal model is often a projection of reality, a selective reflection. As logical models, algorithms also face careful consideration of their scope of application in the real world. For instance, asymptotic complexity analysis reflects the growth trend of an algorithm’s time consumption in an ideal model, but in some scenarios, it doesn’t perfectly align with actual performance.

The most direct factor is device I/O. A single disk I/O can lead to a completely different algorithm design. If we think in terms of a simple model with one computational unit, one read/write head, and an infinite tape, a B-tree would not have a very significant performance advantage over a binary tree, which is usually balanced. However, in database systems, the B-tree requires far fewer queries. Its root can even be kept directly in main memory, and it naturally supports range queries, giving it an enormous advantage.

Another point worth considering is the cache issue. An L1 cache hit is incredibly tempting. For example, compared to a separate chaining hash table, which is common in competitive programming, an open addressing hash table can be much faster in certain scenarios due to the absence of several pointer accesses. This is especially true for linear probing; although the clustering problem is significant, it brings the advantage of CPU prefetching.

Then there are complex concurrent environments. An algorithm or data structure that performs well in a serial model might suffer from severe performance degradation due to lock contention when locks are added in a concurrent setting. The proper use of memory ordering also poses a considerable challenge. The most mind-bending of all are the tricks involved in lock-free data structures. Concurrent programming is inherently ambiguous and relies on a series of primitives for guarantees, making the ideal of lock-free programming beautiful in theory but extremely difficult in practice.

More often than not, an algorithm’s performance is not the only metric. Maintaining a dynamically evolving system is far more difficult than creating a prototype. As the preface to the first edition of Structure and Interpretation of Computer Programs bluntly states: “programs must be written for people to read, and only incidentally for machines to execute.” Sometimes, optimizing an algorithm leads to a significant decrease in overall readability, to the point where it becomes nearly unreadable to others. In such cases, a trade-off must be made based on the team’s situation and requirements. We shouldn’t pay a cost far exceeding the benefit for a minor task. Although this situation is rare (as we are usually just applying existing algorithms and design ideas), every optimization should be approached with caution.

Dealing with algorithms in reality is not as idealized as in competitive programming problems. My optimization target may not always be performance. For example, real-time systems prioritize user experience over throughput. More broadly, we are often faced with trade-offs and balancing acts. While finding multiple solutions to a single problem in competitive programming is a way to train one’s thinking, choosing which solution to use in reality is a real challenge. This brings up a deeper issue: when we examine this from an architectural perspective, the core task is not to identify a pattern in an idealized environment and then select an algorithm we know to solve it.

Every field has thousands of algorithms suited to its specific needs; there is no such thing as the strongest or most efficient algorithm. Learning algorithms is not about accumulating different kinds of algorithms to solve various classes of problems, but about grasping algorithm design patterns. Often, an algorithm from competitive programming is chosen not because of its practical suitability, but because of its suitability for a competition. Take the Red-Black Tree, for example. I personally use it far more often in my daily work than dynamic programming, as every operation on std::set and std::map is implemented with a Red-Black Tree. When a standard becomes the optimization target, we should be cautious. This is especially true for the standardized education of the industrial era, which essentially establishes a standard component and then evaluates individuals based on how closely they match that standard. I don’t believe humans can compete with Large Language Models in this kind of generalized ability for a specific type of task.