DEV Community

Cover image for JS Code Golfing - How To Ruin Everyone's Day
EmNudge
EmNudge

Posted on • Edited on

JS Code Golfing - How To Ruin Everyone's Day

The title of this article is a little tongue-in-cheek.
Code golfing is a term used to describe the practice of getting as few "points" (as you do in golf) as possible by using as little code as possible.

Javascript is a fantastic language for code golfing due to backwards compatibility, quirks, it being a high level language, and all the coercion. We're going to go over some extreme JS code golfing examples and explain how and why they work.

While extreme code golfing can quickly make your codebase an unreadable mess, occasionally certain aspects of code golfing can make some more complex-looking code much more readable. Understanding code golfing can also give you a better understanding of some concepts in JS.

The Basics

Here we're going to go over some code golfing tricks that I'm sure you've seen before. This includes:

  1. Shorten variable names.
  2. Don't use intermediate variables for simple operations.
  3. Avoid blocks when possible (if, while, and for statements).
  4. Don't unnecessarily use else statements after a return.
  5. Use arrow functions when possible.

Let's give a code snippet and then shorten it using these 5 basic rules.

// 345 characters
function getNumOneOrZeroOff(baseNumber, shouldTryToGoUp, falsyOrTruthyVal) {
  const myBoolean = Boolean(falsyOrTruthyVal);
  const oneOrZero = Number(myBoolean);

  if (shouldTryToGoUp) {
    const numberPlus = baseNumber + oneOrZero;
    return numberPlus;
  } else {
    const numberPlus = baseNumber - oneOrZero;
    return numberPlus;
  }
}
Enter fullscreen mode Exit fullscreen mode

Besides the fact that this is a pretty useless function, it's kind of unnecessarily long.

Let's implement the first 5 basic rules to shorten this up a bit.

// 164 characters
const getNewNum = (num, goUp, arbitraryVal) => {
  const oneOrZero = Number(Boolean(arbitraryVal));

  if (goUp) return num + oneOrZero;
  return num - oneOrZero;
}
Enter fullscreen mode Exit fullscreen mode

shortened variable names
removed const myBoolean = ;{}else{}, whitespace, and lines 7,10

Wow, despite us removing a lot of the super specific variable names, it actually got way easier to read. Let's make this even shorter with 4 more basic rules.

  1. Prefer unary !! instead of Boolean().
  2. Prefer unary + instead of Number().
  3. Prefer ternaries over if statements when possible.
  4. Shorten ternaries to the delta of the 2 expressions.
// 136 characters
const getNewNum = (num, goUp, arbitraryVal) => {
  const oneOrZero = +!!arbitraryVal;

  return num + (goUp ? oneOrZero : -oneOrZero);
}
Enter fullscreen mode Exit fullscreen mode

removed Number(Boolean())ifreturn;num and whitspace
added +!!?:

Number 4 might have been a bit confusing. What that means is instead of doing return goUp ? num + oneOrZero : num - oneOrZero, we do return num + (goUp ? oneOrZero : -oneOrZero) since they both share a common factor (num). The delta is the difference of the 2 expressions - the only thing changing.

There is only a 3 character difference (in our case), but this stuff adds up and can improve readability. We could also remove the parens for an additional 2 characters less, but it would be difficult to read.

Let's stop caring about readability for now.

Obfuscation by Brevity

Reusing the previous snippet, we can employ some more rules.

  1. Remove unnecessary whitespace.
  2. Remove unnecessary parens.
  3. Remove unnecessary semi-colons
  4. Use single character variables
  5. Prefer let over const.
// 43 characters
let f=(a,b,c)=>{let d=+!!c;return a+(b?d:-d)}
Enter fullscreen mode Exit fullscreen mode

shortened variables names
removed constconst;() and whitespace
added letlet

Here is where all the talk about code golfing improving readability goes straight out the window. It's practically indecipherable.

What if we stopped caring about performance as well?

  1. Prefer reevaluating expressions if it saves space
  2. Prefer creating global variables over defining with let, const, or var (assuming the lack of "use strict")
// 26 characters
f=(a,b,c)=>a+(b?+!!c:-!!c)
Enter fullscreen mode Exit fullscreen mode

removed let{}returnletdd+ and whitespace
added !!c

by placing the +!! inside our ternary, we were able to remove a variable assignment, thus allowing a one-line return. In arrow functions with only a body containing a return statement, we can remove the braces.

We were also able to use -!! instead of -+!! because unary negation exists.

So throwing out almost all readability and best practices, we have decreased a function from 345 characters, all the way down to 26 - less than 8% of its original size! Wow.

Let's go a bit further and uncover some not as often used JS tricks.

Expression Evaluation

In a certain sense, all functions and assignments are expressions. Many times functions will return undefined, but it's still something. This gives us a lot of power to shorten our code.

Time to dive into some more snippets!

// empty function
const myFunc = () => {};

myFunc()       // -> undefined
!!myFunc()     // -> false
4 + +!myFunc() // -> 5

// assignments as expressions
let a = 1;
let b = 1;
let c = 1;
c += b += a += 1;

a // -> 2
b // -> 3
c // -> 4

// 2 operations at once
let i = 0;
console.log(i++); // logs 0 and now i is 1
console.log(++i); // logs 2 and now i is 2
Enter fullscreen mode Exit fullscreen mode

It should be noted that declarations do not return anything (not even undefined) and therefore are not expressions. You cannot log let a = 3 or use it anywhere in an expression (but you can do let a = b = c).

With the knowledge that these are all expression-able (new word), let's involve an oft-forgotten piece of JS. The following is valid JS:

// ignore this. Just initializations.
let a = 0, b = 0, myFunc = (num) => console.log(num);

let c = (a++, b+=a, myFunc(2), a+=b, b + a); // > 2
c // -> 3
Enter fullscreen mode Exit fullscreen mode

What just happened here? This is behavior you might be familiar with if you've use C++. In JS, we can write comma separated expressions inside parentheses. The expressions are evaluated left-to-right and the right-most expression is then returned.

In our case, we did a lot of things that we might have otherwise done on their own line.

When is this useful? Well it wouldn't be very useful in most circumstances, since we could just ditch the parens and use semicolons instead. The most useful place is in while loops, for loops, and shortening arrow functions.

// prints 0-9 inclusive
let i = 0;
while (console.log(i++), i < 10);

// prints 0-9 inclusive
for (j = 0; console.log(j++), j < 10;);

// 32 characters
a=>{r=0;a.map(n=>r+=n);return r}
// 25 characters
a=>(r=0,a.map(n=>r+=n),r)
// a reduce would do the same thing and be only 23 characters, I know
Enter fullscreen mode Exit fullscreen mode

By the loops, we don't even need the parens; they're optional. We have created fully functional for and while loops with no body. Make sure to use a semicolon so that the loops don't accidentally loop some random statement right below it.

It should be noted that we're also able to leave out parts of a for loop as long as the parens of the for loop contain 2 semicolons. Due to our expressions, the last section of the parens (after the 2nd semicolon) is essentially useless as long as our last expression is a boolean (or evaluates/coerces to one).

We can also use turn non-expressions into expressions using the evil eval(). It's generally recommended to avoid it, but there are some use cases, such as code golfing. It always returns the last expression, so we can use it to decrease an arrow function by 3 characters. We're saving pennies at this point, but it might be worth it in the long term.

// 43 characters
a=>{for(var o="",i=0;i<a;i++)o+=i;return o}

// 40 characters
a=>eval('for(let o="",i=0;i<a;i++)o+=i')
Enter fullscreen mode Exit fullscreen mode

Taking Advantage

There are a lot of tricky and quirky parts of JS which reveal some pretty interesting behaviors. We can take advantage of these behaviors to shorten our code.

The more common of these tricks is using bitwise operators to coerce floating point numbers into integers. Usually we would use something like Math.floor(), Math.ceil(), or Math.round(), but these take up far too many characters.

We can use bitwise operators, which have a side effect of truncating decimals, to perform a similar behavior with only 2 characters. By using ~~ or |0 we can perform bitwise operations that don't do anything. Since they truncate when calculating the result, we get a number without decimals.

// 31 characters
Math.floor(Math.random() * 100)

// 21 characters
~~(Math.random()*100)

// 19 characters
Math.random()*100|0
Enter fullscreen mode Exit fullscreen mode

2 things to note: 1. truncation will get different results than Math.floor() when dealing with negative numbers. 2. bitwise operators are performed in the same order-level as multiplication. Think PEMDAS, but stick a B next to the M or D. This is why the second bitwise example didn't need parens, but the first did.

Another behavior which you may be familiar with is short circuit evaluation. It deals with the && and || operators and allows us to save a lot of space.

// logs thing and then returns it
const logIt = thing => (console.log(thing), thing)

logIt(0) || logIt('hey') // logs both since 0 is falsy
logIt('hey') || logIt(0) // only logs 'hey' since 'hey' is truthy

logIt('hey') && logIt(0) // logs both since 'hey' is truthy
logIt(0) && logIt('hey') // only logs 0 since 0 is falsy
Enter fullscreen mode Exit fullscreen mode

It's used many times to execute one or both functions depending on the return value of the first. If you want the second one to execute only if the function is truthy, use &&. If you want the second one to execute only if the function is falsy, use ||.

&& and || can also be used to retrieve the falsy or truthy value.

const theFalsyOne = '' && 100; // it is ''
const theTruthyOne = '' || 100; // it is 100
Enter fullscreen mode Exit fullscreen mode

If both are truthy, && will return the second value and || will return the first one. If both are falsy, && will return the first value and || will return the second. This behavior is due to short circuit evaluation as well.

The last behavior surrounds valueOf. There was an interesting question about if (a==1 && a==2 && a==3) can ever evaluate to true and the answer had to do with valueOf as well.

We can create objects that appear to be primitive values when used in concatenation and mathematics. If we use an object in those circumstances, JS will check out its valueOf property to coerce it into a primitive. We can do some really cool stuff with this, but I've found the most common use to be for Math.random()

const makeEllipse = (x, y, width, height) => { 
  // do stuff
}

// 91 characters
makeEllipse(Math.random() * 50, Math.random() * 50, Math.random() * 10, Math.random() * 10)

// 60 characters
let r={valueOf:Math.random}
makeEllipse(r*50,r*50,r*10,r*10)
Enter fullscreen mode Exit fullscreen mode

There is obviously a trade-off when you have to define a new object and include the original function, but if you're using it enough, it helps to shorten it.

Saving Pennies

In real code golf competitions, every character counts. If you can shave off a single character, do it. Here are some techniques to spare characters here and there.

Concatenate with ${} instead of ++ when in between 2 strings. This saves a single character.

// 27 characters
'You are '+age+' years old'

// 26 characters
`You are ${age} years old`
Enter fullscreen mode Exit fullscreen mode

You can use regular functions as tagged template literals as long as the function uses the string it asks for as a string. This can save 2 characters.

// 16 characters
myArr.split('.')

// 14 characters
myArr.split`.` 
Enter fullscreen mode Exit fullscreen mode

This cannot work with things like eval since they don't use the input as if it were a string. You must also try to not include any ${} since tagged template literals receive the variables in different parameters.

If you have an infinite loop, use for(;;i++) over while(true)i++. This saves 4 characters. i++ in this case is the expression or function which gets called every iteration.

Lastly, Numbers can also get called using scientific notation. Instead of 1000, you can use 1e3 which will evaluate to the same thing. This saves one character starting at 1000, but the ROI quickly goes up with larger numbers.

Closing Remarks

Code golfing can be a lot of fun. The practice can also teach you a lot about the language.

I obviously could not cover all the JS code golfing tricks, but I hope I covered a decent chunk. I highly recommend checking out this thread for some more JS code golfing tricks.

If you want to start code golfing, I highly recommend checking out https://codegolf.tk/ and https://www.dwitter.net/
Those are sites which use code golfing and canvas to create beautiful visualizations with an absurdly minimal amount of characters.

Confused? Have some more tips to share? Feel free to leave a comment and let me know!

Top comments (4)

Collapse
 
mariamozgunova profile image
Maria Mozgunova • Edited

Cool!
Just learned some interesting things about JS
It was a bit unexpected that '' && 100 would return an empty string. I thought the result would be false.

Was interesting to find out that bitwise operators could work like Math functions.

Collapse
 
lexlohr profile image
Alex Lohr

Reminds me of Jed's 140byt.es, now discontinued. My first golf was a base64 decoder.

Collapse
 
sinrock profile image
Michael R.

Awesome 👍

Collapse
 
ahkohd profile image
Victor Aremu

😃