ThomasFriday.com | How to code golf in JavaScript

The goal of "code golf" is to reduce the size of a program as much as possible. It's a fascinating practice that I knew very little about. To change that, I recently challenged myself to code golf a simple JavaScript program.

In this article I'm sharing the program I wrote, and the 6 main stages I went through to make it much shorter. This should give a good overview of the main techniques used to code golf in JavaScript.

The starting point

Here's the code I started with. It's using the canvas to draw a moving square. That's not very impressive, but that's enough for our purpose.

// Retrieve the canvas and its context
let canvas = document.getElementById('c');
let ctx = canvas.getContext('2d');

// Set the canvas size
canvas.width = 100;
canvas.height = 100;

// Variable to store the x position of the square
let x = 0;

// Main function that draws the square and moves it
loop();

function loop() {
  // Clear the canvas, so we can draw on it later 
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Change the position of the square
  if (x > canvas.width) {
    // If it's gone too far, move it to the far left
    x = -20;
  } else {
    // Otherwise, move it slightly to the right
    x = x + 2;
  }

  // Draw the black square at the position x
  ctx.fillRect(x, 20, 20, 20);

  // Call the loop function about 60 times per second
  requestAnimationFrame(loop);
}

Without comments, that's 344 characters. How short do you think it can become?

Stage 1: the basics

Let's start with some basic techniques to shorten the code:

Removing spaces, semicolons, and new lines will be done near the end to keep the code more readable for now.

With all these changes, the program looks like this.

c = document.getElementById('c');
t = c.getContext('2d');
c.width = 100;
c.height = 100;
x = 0;

loop();

function loop() {
  t.clearRect(0, 0, 100, 100);
  x = (x > 100) ? -20 : x + 2;
  t.fillRect(x, 20, 20, 20);
  requestAnimationFrame(loop);
}

Character count: from 344 to 247.

Stage 2: DOM and canvas tricks

After some research I discovered 3 tricks related to the DOM and the canvas that will make the code significantly shorter.

The first one is about getElementById(). It turns out it's not necessary to use this function at all, because JavaScript automatically creates variables for all HTML elements with an id. So to access the canvas that has id="c", we can directly use the variable c.

The second trick is about requestAnimationFrame(). This is a powerful function to refresh the canvas about 60 times per second, but it's also quite long to type. Instead we should call setInterval() that works pretty much the same and is a lot shorter.

The last trick is about clearRect(). There is a way to clear the canvas without clearRec(): by setting the size of the canvas. So if we put c.width = 100 directly inside the loop() function, the canvas will be cleared at every frame.

Here's the new code with the 3 tricks in place.

t = c.getContext('2d');
x = 0;

setInterval(loop, 20);

function loop() {
  c.width = 100;
  c.height = 100;
  x = (x > 100) ? -20 : x + 2;
  t.fillRect(x, 20, 20, 20);
}

And now that we are using setInterval(), let's replace loop() by an anonymous function.

t = c.getContext('2d');
x = 0;

setInterval(() => {
  c.width = 100;
  c.height = 100;
  x = (x > 100) ? -20 : x + 2;
  t.fillRect(x, 20, 20, 20);
}, 20);

Character count: from 247 to 154.

Stage 3: smarter variables

If we use our variables in smarter ways, it's possible to save a few characters. Here are some ideas:

x = setInterval(() => {
  c.width = c.height = 100;
  x = (x > 100) ? -20 : x + 2;
  c.getContext('2d').fillRect(x, 20, 20, 20);
}, 20);

And there are 2 more things we should do:

These 2 changes are actually making the code slightly longer. But soon, when we will remove spaces and semicolons, these modifications will become worthwhile.

k = 20;
x = setInterval(() => {
  w = c.width = c.height = 100;
  x = (x > w) ? -k : x + 2;
  c.getContext('2d').fillRect(x, k, k, k);
}, k);

Character count: from 154 to 141.

Stage 4: merge lines

When a variable is assigned in JavaScript, the value of the variable itself is returned. For example, when doing k = 20, we are setting k to 20 and we are also returning the value 20. We can use this fact to combine two lines into one, like this.

// Before
k = 20;
x = setInterval(/* ... */, k);

// After
x = setInterval(/* ... */, k = 20);

With the same idea, we merge these two lines and remove the variable w.

// Before
w = c.width = c.height = 100;
x = (x > w) ? -k : x + 2;

// After
x = (x > (c.width = c.height = 100)) ? -k : x + 2;

And also these two lines.

// Before
x = (x > (c.width = c.height = 100)) ? -k : x + 2;
c.getContext('2d').fillRect(x, k, k, k);

// After
c.getContext('2d').fillRect(x = (x > (c.width = c.height = 100)) ? -k : x + 2, k, k, k);

Now our anonymous function contains a single line of code.

x = setInterval(() => {
  c.getContext('2d').fillRect(x = (x > (c.width = c.height = 100)) ? -k : x + 2, k, k, k);
}, k = 20);

Character count: from 141 to 126.

Stage 5: clean up

What we have so far is quite compact, yet there are some unnecessary characters in it. Removing them won't change anything, except making our code shorter. So we should delete:

x=setInterval(()=>{c.getContext('2d').fillRect(x=x>(c.width=c.height=100)?-k:x+2,k,k,k)},k=20)

Character count: from 126 to 94.

Stage 6: last tricks

There are 3 remaining tricks!

First, if we read closely the documentation for setIterval(), we will discover that the callback parameter can either be a function or a string. So we replace ()=>{...} by "..." to win 4 chars.

Second, we should take advantage of the "tagged template" ES6 feature by changing c.getContext('2d') into c.getContext`2d`. That's shorter by 2 chars.

And third, it's possible to change the number 100 into 99 to save one char.

Here is the final result:

x=setInterval("c.getContext`2d`.fillRect(x=x>(c.width=c.height=99)?-k:x+2,k,k,k)",k=20)

Character count: from 94 to 87.

The original code had 344 characters. Now it's about 4 times shorter for the same result!

Bonus

We only talked about the JavaScript so far, but what about the HTML?

Here's the code for both the HTML and the JavaScript, in only 117 chars. I added some line breaks below only for legibility.

<canvas id=c>
<script>
  x=setInterval("c.getContext`2d`.fillRect(x=x>(c.width=c.height=99)?-k:x+2,k,k,k)",k=20)
</script>

If you save this tiny program in an HTML file and open it in a browser, you will see this result.

Conclusion

Going from 344 characters to just 87 was a fascinating journey. Multiple times I thought "there's no way to be shorter", but I was always wrong. And there may be ways to make the code even smaller.

It took me about a week of work and the help of a friend to get to the last solution. I learned a lot about JavaScript with this challenge, and I hope you enjoyed reading about it.