Programming contests are good for me (and probably for you too)

Criticism of programming contests

I have come across numerous instances of criticism of competitive programming and programming contests. To summarize, the criticism boils down to these points:

  • Competition-style tasks during job interviews are the wrong way to filter candidates;
  • Competitive programming does not give a relevant experience in software engineering;
  • Competitive programming is detrimental to software engineering work.

I must say upfront that I agree with the first point. The problems that are used in programming contests are quite different from the tasks that are normally encountered in day-to-day work.

But it is the other points that I want to address. Programming contests give a lot of skills and experience that are usually quite hard to obtain elsewhere. The programming contests were transformative for my programming skills.

Typical structure of a contest

Programming contests are designed to test and challenge the skills of designing and implementing algorithms. They are relatively short timewise (usually around 5 hours). In some contests, teams compete while in others individuals do.

During the contest, the participants are offered a small number of problem statements to solve. Participants may solve tasks in any order.

A problem statement

A problem statement is a non-ambiguous description of a task to solve.

The solution to the problem is a source code for a program that reads input data (that describes a specific instance of a problem) from STDIN and outputs the solution STDOUT.

The time and memory that the solution may use are limited. The constraints on time and memory are always present in the problem statement. Since time and memory are constrained, the problem statements also include limits on possible input data such as the number of elements, maximum values for numeric values, and so on.

The problem statement also includes several instances of input data with the correct corresponding output data (collectively called “a test case”) to serve as examples.

Scoring and submissions

Having coded the solution, a participant submits it for grading. The grading is automated and the result is available in a short time (usually in about a minute). The grading system compiles the source code and runs the program on many test cases and checks its output. The result of a run on some test is either “OK”, “Wrong Answer”, “Runtime Error”, “Time Limit Exceeded” or “Memory Exceeded” (result names are self-explanatory). In case of a failure, the participant will only know the number of the first failing test case without any other specifics.

There are 2 distinct methods to score solutions:

  1. If the solution passes all tests, it gets a score.

    The time to solve is also taken into account. So the participants are ranked first by the number of solved tasks (or by the sum of scores) and then by the sum of submission times. Many competitions, including the ACM ICPC, use this scoring style.

  2. Each passed test case gains some amount of score.

    This style is not as popular, though some beginner-friendly competitions do use such a style.

The specific skills that contests help level up

Here's a list of skills that are required for successful participation in a programming competition.

  1. Data Structures and Algorithms

    This is the obvious one.

    The problems are primarily designed to practice the knowledge of algorithms and the skill to combine them or invent new ones on the spot.

  2. Attention to Details

    Many problems and algorithms have numerous edge cases that must not be missed. Sometimes a tiny detail might mean a difference between either a trivial or a very complicated algorithm.

    All participants are trained to read the task descriptions carefully and not miss any important details.

  3. Selection of appropriate algorithm based on the scale and complexity of a problem.

    Oftentimes it is possible to solve a problem using either of several different algorithms, and one needs to pick an appropriate algorithm. It takes a certain skill to be able to pick an algorithm based on a task description. Some factors that affect algorithm selection are:

    • The ease of the implementation of an algorithm.

      It makes no sense to use an advanced algorithm (or a data structure) when a simpler one would do. The complexity theory, big-O notation together with some well-known scale constants help here.

    • Time and memory constraints, input size, and specifics of the input data.

      The bigger the input size, the more efficient the algorithm has to be.

      The amount of memory and time that the algorithm takes depends on the size of an input (e.g., number of elements in the input), limits on numeric values (e.g., this is very important for dynamic programming algorithms), or some properties of an input (e.g., the number of distinct values, number of graph components, presence of cycles, etc.).

    • Algorithm assumptions and special properties of input data.

      Some algorithms may only be applied if an input satisfies some criteria. E.g., Dijkstra's algorithm won't work on graphs with negative edges, and heap-based algorithms won't work if we need to preserve the order of elements.

  4. Thorough testing of code and algorithms

    The strict grading procedure quickly teaches to only submit tested solutions, since the code that has not been thoroughly tested does not have a chance to be accepted.

    Since the tasks and algorithms are quite complex, manual example-based testing does not suffice, and more advanced testing techniques are required.

    Some of the commonly employed techniques are:

    • randomized and property-based testing

    • assertions for the invariants

    • oracle-based testing. Some common oracles are:

      • alternative (usually more trivial) implementation of an algorithm
      • generating the input data together with the correct answer or in such a way the correct answer is known
  5. Staying focused in high-stress conditions

    Competitions are always stressful. Contests can be quite intense, especially when the time is running out.

    It is natural to do “shotgun debugging” when under stress, i.e. non-systematically trying some random ideas without first acquiring an understanding of the nature of the bug in hopes that it fixes the bug. Instead, we must be methodical and keep using proven working techniques since there is no other way.

How the structure of contests helps acquire skills

Rapid feedback

We learn and gain experience by doing something and seeing if it works or not. This requires that the feedback loop is quick and gives good and actionable feedback.

The feedback loop in competitions is orders of magnitude faster and more precise compared to typical software engineering projects. Commercial software projects are lengthy. It might take months or years to see all the latent bugs that may be lurking in the codebase. Competitions provide near-instantaneous feedback - one quickly learns to test the code and which testing techniques are quick and reliable.

Commercial projects are long, complicated, and have a lot of parts and participants. It might be hard to learn from experience on real-world projects since:

  1. It usually takes a significant time for the code to get from a developer to exercising in production of all the functionality including the edge cases One may even not work on this project anymore.

  2. Mistakes are costly.

    Therefore projects have a lot of redundancy and fail-safety mechanisms that mask some problems in the software.

    It is very beneficial for real-world software to have mechanisms such as retries, self-healing, and periodic reboots. But for education, the software should fail loudly and explicitly – so we can learn not to make the same mistakes again.

    In programming competitions, such solutions simply won't be accepted.

  3. It is hard to attribute the success or failure of a project to specific parts of the software or to the work of specific individuals or teams.

Strict solutions checking with tight time and memory constraints

The test suites for problems have very good coverage and it is almost unbelievable how well they can find bugs and flaws in solutions.

It is virtually impossible to have a buggy or inefficient solution accepted since the grading system checks correctness and efficiency.

Hiding of failed tests, penalties for failing submissions

Since a participant does not see the failing test case, the attempts to brute-force the solution are infeasible.

Penalties for faulty submissions encourage participants to not rely on any provided tests. Instead, participants must implement their own testing. Since the contest time is limited, this testing must also be effective and must not take too much time.

Conclusion

Based on all of the above,

  1. competitive programming is not the same as real-world software engineering;

  2. nonetheless, the training for programming competitions can immensely improve some of the software engineering skills;

  3. To learn efficiently, it is very useful to have good feedback loops.

    The faster and more accurate they are, the easier it is to learn. Contests offer good feedback loops for some of the kills, for some of which there are a few alternatives.