Compilers: History and purpose
How does written code become something a computer can execute? To answer that, we first need to look at the tools and processes that translate it.

As a starting point for this series, we’ll go over basic compiler terminology, the evolution of code translation, and a few JavaScript peculiarities. That’ll give us a solid foundation to build on.
The language barrier: Why compilers exist
Conceptually, computers function differently from humans, operating exclusively on binary values. This means that they only understand the values of “0” and “1”, along with a clearly defined and limited set of operations they’re able to perform on those values.
Humans, on the other hand, communicate using natural languages, systems full of implicit rules, cultural context, and vague definitions.
Programming languages were invented as a bridge between these two opposites. They take a tiny subset of everyday vocabulary and the most basic semantics of spoken languages, and tie them down to a strict structure and a predefined shape, allowing a computer to unequivocally execute a command.
But because this code is still completely alien to machines, we need tools to translate it into machine code (which computers actually understand). These tools are called “compilers”, and the process is called “compiling”.
Optimizing the executed code
By their very nature, compilers have intimate knowledge of the hardware architecture the program will be executed on. Because of this, they can reformulate blocks of code to execute faster, safer or more correctly. Automating these tasks has significantly increased developer productivity, since an entire segment of work that previously had to be done manually has now been automated.
Why isn’t there just one universal compiler?
When it comes to compiler development, some of the parameters taken into account are: compilation speed, memory usage during compilation and execution, flexibility when it comes to architectures supported (Intel and Apple CPUs have a completely different supported instruction set, for example). In addition, each programming language that the compiler is written for has its own specificities, which will then determine what’s possible and realistic. Taking all this together means that there can’t be one ideal compiler. Rather, it’s a matter of compromise, and depending on the purpose, different parameters will carry different weights.
Different implementations
Even if all of the starting parameters are identical, a compiler can be made in countless different ways. Consequently, programming languages can have multiple compilers, often as competing solutions but occasionally as specialized variants (a compiler targeting microcontrollers, for example). For JavaScript, which is what the focus of the series will ultimately be, the most widespread ones are V8 (in Chromium), JSC (JavaScriptCore, in WebKit) and SpiderMonkey (in Firefox). It’s worth mentioning Hermes too, made with very different requirements in mind (used within React Native), though we won’t cover these niche-filling ones.
As JavaScript is a very open and, by now, very well documented language, that open nature is also reflected in its compilers, with quite detailed technical documentation available for all of them.
Compilers: AOT (Ahead of time) vs JIT (Just in time)
The most basic way to divide compilers: Those that compile code ahead of time, and those that do it on the fly (Just in time).
Ahead of time (AOT)
The initial idea, and what we already explained, where the code is compiled upfront, on a developer’s machine, outputting an optimized executable file that’d be distributed to end users. For example, Fortran, C and C++ belong to this category.
This approach is suitable for stricter languages, where it’s possible to determine, in advance, just how much memory is needed throughout execution, and data management is thus visibly more precise.
Just in time (JIT)
Invented just a few years after AOT, with Lisp. However, as it never gained wider popularity, the approach itself was largely forgotten, up until Java was released, as it served as a massive popularizer of the term and practice.
With JIT, a compiler on the developer’s machine generates so-called bytecode. It’s a sort of an intermediate format, universal for all architectures and suitable for optimization. The main portion of compilation, where optimizing is also performed, is then happening on the user’s device.
The advantage of taking this route is that, at the moment of execution, we have a lot more information pertaining to data we’re working with, amount of free RAM, amount of available processing and a host of other factors. This can make optimization significantly more efficient.
The limitations, of course, are reflected in the fact that this needs to be done extremely quickly, as the application is effectively blocked while this process is finished.
It’s also worth mentioning that, for most apps, the same blocks of code are executed multiple (dozens of) times, while this compilation penalty occurs only once. For every subsequent execution, we just use cached compiled code.
With that in mind, so that the time spent on compiling as opposed to executing code is kept to a minimum, a great effort was put into making well-informed calls on which optimizations are worth performing, if any, and which blocks of code should be prioritized for compiling.
This approach is especially appropriate for dynamic languages (one of which is JS), where so little is known ahead of execution.
Interpreters versus Compilers
For a long time, the code was compiled on the developers’ computers. That’d take a while, but was done once (per targeted CPU architecture). The resulting file(s) would then be distributed to end users.
Interpreter executes immediately
With interpreters, we turn this process around, having the authored code translated into machine code at the moment of execution, without any prep work. This shifts the burden of computing resources onto the client, who then, in turn, has a translator adapted to the architecture.
Taken together, an interpreter will utilize more resources, as the code will be translated on every single device, every time it’s executed, as opposed to doing it once, in advance. While this might seem like a clear win for compilers, there are several advantages the interpreters have.
Interpreters are simpler to make
As they don’t have to optimize anything, and are targeting one architecture only, it’s just easier to create an interpreter. The only requirement being: Create a 1:1 mapping between written and machine code. It’s then executed line by line, as the application is running.
Easier to share software
One of the reasons for JS popularity is that the browsers get the code exactly as originally written, in a human-readable format. They then take it upon themselves to determine the best way of turning that into machine code. This meant that anyone could write JavaScript, so adoption was swift, and ideas were spreading without hurdles.
Performance penalty
On the other hand, as one can imagine, a consequence of direct translation, without looking at the wider picture and practically without optimizations, is significantly lower performance. This impact is significant, with compiled code often being more than 10x faster than interpreted.
While it’s clear that this is a huge difference, in practice, it often doesn’t matter. Most code is rarely executed and short running, and the CPUs are fast enough that the user wouldn’t even notice the difference.
Conclusion
With this, we’ve laid the groundwork to better explain what JavaScript originally did, how it evolved, and what it does now, related to how it’s translated into machine code. The next article in the series will cover a short history of JavaScript, to tie in the subject from that side too.
Let’s start achieving excellence together
Get in touch with our experts today to turn your ideas into reality and accelerate business growth.



