Puzzle Practice

Writing Programs Under a Size Limit

Strategies for fitting a program into a small number of characters or grid cells: planning, reuse, reshaping the algorithm, and knowing when to stop.

On this page
  1. A budget changes how you think
  2. First, get it working
  3. Measure exactly
  4. Strategies that apply almost everywhere
  5. Tricks that suit grid languages
  6. Know the cost of cleverness
  7. When to stop

A budget changes how you think

A size limit turns programming into a budgeting problem. Instead of asking “what is the clearest way to do this?”, you ask “what is the cheapest way?” The limit might be a number of characters, bytes, or cells on a grid. Whatever the unit, every symbol now has a cost, and you start noticing how much of ordinary code is convenience rather than necessity.

Size-limited puzzles are popular because they are easy to state, easy to measure and endlessly refinable. They are close cousins of code golf, though a limit with a pass-or-fail threshold feels different from a race to the smallest number: you aim to get under the bar, not necessarily to win.

First, get it working

The strongest advice is also the least exciting: write a correct version before you start shrinking. A short program that does not work is worth nothing. Begin with the plain solution, test it on a few inputs, and keep it as a reference.

Then shrink in small steps, retesting after each one. If something breaks, you know the last change was responsible. Keep every version so you can go back. It is surprisingly easy to lose a working layout while chasing one more saved character.

Measure exactly

Be sure you know how the limit is counted. Do newlines count? Spaces? Are characters counted or bytes? In a grid language, does the limit apply to the bounding rectangle, including empty cells, or to the number of non-space characters? The answer can change the best strategy completely. If the rules are unclear, ask the organizer, or state your reading when you submit.

Strategies that apply almost everywhere

  • Reconsider the algorithm first. Micro-tricks save a few characters, but a different approach can save many. A formula may replace a loop, or counting down may replace counting up and comparing.
  • Reuse code. If two parts of the program do almost the same thing, can one section serve both? In a grid language, a path that is traversed twice, in different contexts, costs half as much.
  • Eliminate setup. Variables you never need to name, initial values that are already the default, and output that can be produced as a side effect of another step are all candidates for deletion.
  • Use what the language gives for free. Many languages initialize memory or stack contents to zero, or return a default value when something is empty. Relying on that can save characters, but be sure the behavior is guaranteed, not an accident of one interpreter. Edge cases around empty values, such as a stack underflow that quietly returns zero in some implementations, are a common source of portability problems.
  • Shorten constants. Large numbers can often be built from smaller operations. When a character code is needed, arithmetic may be shorter than a literal, or the other way round.
  • Accept simpler output. If the rules allow a trailing newline or leave the format loose, do not spend characters on exact formatting you do not need.

Tricks that suit grid languages

Grid languages have their own set of savings, because geometry is part of the program.

  • Let the pointer do double duty. A single row can be run left to right and then, after a turn, right to left, using the same cells for two phases. This is the idea behind many compact loops built from geometric loops.
  • Fold the program. Instead of one long line, fold it into a small block with arrows at the corners. If the limit is on cells counted in a bounding box, a compact square can beat a long strip. If only non-space characters count, then layout is free and you can spread out to avoid collisions.
  • Use wraparound. Because the edges connect in a toroidal playfield, the pointer leaving one side reappears on the other. This can close a loop without spending an arrow on the return path.
  • Skip cells deliberately. The trampoline instruction lets the pointer jump over a cell, so a single symbol can be both executed on one pass and skipped on another.
  • Exploit string mode. Pushing a run of characters at once is often cheaper than pushing them individually, which is the reason string mode appears in many short programs.
  • Keep the stack as your memory. Using the program stack for values instead of writing them to cells avoids the cost of get and put instructions.

As a small illustration, a classic compact way to print a message in Befunge pushes a newline code with 64+, then the text in quotes (written in reverse so that it prints in the right order), and finishes with >:#,_@, which repeatedly duplicates the top value, prints it if nonzero, and ends when it reaches zero. The whole thing is only a line or two, because the loop, the string and the termination test share cells. Notice how the arrow, the skip and the conditional all cooperate to save space.

Know the cost of cleverness

Compact code is harder to read, harder to debug and easier to break. Whenever you rewrite for size, leave a note, outside the counted program, saying what each section does. When you come back later, the notes will save you from re-deriving the trick. And use tracing by hand when a shortened version behaves strangely; shrinking changes timing and flow in ways that are hard to see without following the pointer.

It also pays to balance size against the other rules. If a challenge scores on more than one thing, such as size and speed, the shortest solution may be slow. Think about scoring rules before you commit to a particular trade-off.

When to stop

There is always another character to save, and chasing the last few can consume hours. Decide in advance how much effort the puzzle deserves. Once you are under the limit and the program is verified, you can stop, or move on and come back later; ideas often arrive when you are not looking. Comparing your result with other people’s solutions is a good way to find out what you missed, and many of the best tricks are learned that way.

The complete guide to puzzle practice

Puzzle Programming Practice
Read the guide

Keep exploring

Guides from other topics

All topics →