Just-in-Time vs Ahead-of-Time

Just-in-time and ahead-of-time are two different approaches for deciding when to do work.

Say we're writing a method that takes in a number n between 2 and 1,000 and checks whether the number is prime.

One option is to do the primality check when the method is called:

private static boolean isPrimeBruteForce(int n) { int highestPossibleFactor = (int) Math.sqrt(n); for (int potentialFactor = 2; potentialFactor <= highestPossibleFactor; potentialFactor++) { if (n % potentialFactor == 0) { return false; } } return true; } public static boolean isPrime(int n) { return isPrimeBruteForce(n); }

This is a just-in-time approach, since we only test a number when we've received it as input. (We determine whether n is prime "just in time" to be returned to the caller.)

Another option is to generate all the primes below 1,000 once and store them in a hash set. Later on, when the method is called, we'll just check if n is in that hash set.

import java.util.Collections; import java.util.HashSet; import java.util.Set; private static boolean isPrimeBruteForce(int n) { int highestPossibleFactor = (int) Math.sqrt(n); for (int potentialFactor = 2; potentialFactor <= highestPossibleFactor; potentialFactor++) { if (n % potentialFactor == 0) { return false; } } return true; } private static final Set<Integer> PRIMES; static { HashSet<Integer> primes = new HashSet<>(); for (int potentialPrime = 2; potentialPrime <= 1000; potentialPrime++) { if (isPrimeBruteForce(potentialPrime)) { primes.add(potentialPrime); } } PRIMES = Collections.unmodifiableSet(primes); } public static boolean isPrime(int n) { return PRIMES.contains(n); }

Here we're taking an ahead-of-time approach, since we do the calculations up front before we're asked to test any specific numbers.

So, what's better: just-in-time or ahead-of-time? Ultimately, it depends on usage patterns.

If you expect isPrime will be called thousands of times, then a just-in-time approach will do a lot of repeat computation. But if isPrime is only going to be called twice, then testing all those values ahead-of-time is probably less efficient than just checking the numbers as they're requested.

Decisions between just-in-time and ahead-of-time strategies don't just come up in code. They're common when designing systems, too.

Picture this: you've finished a question on Interview Cake and triumphantly click to advance to the next question: Binary Search Tree Checker. Your browser issues a request for the question in Java.

There are a few possibilities for what happens on our server:

  • One option would be to store a basic template for Binary Search Tree Checker as a starting point for any language. We'd fill in this template to generate the Java version when you request the page. This is a just-in-time approach, since we're waiting for you to request Binary Search Tree Checker in Java before we do the work of generating the page.
  • Another option would be to make separate Binary Search Tree Checker pages for every language. When you request the page in Java, we grab the Java template we made earlier and send it back. This is an ahead-of-time approach, since we generate complete pages before you send a request.

On Interview Cake, we take an ahead-of-time approach to generating pages in different languages. This helps make each page load quickly, since we're processing our content once instead of every time someone visits a page.

. . .