According to their definitions, the difference between a compiler and an interpreter seems clear enough:

  • interpreter is a program that directly executes instructions written in a programming language
  • compiler is a program that transforms source code in a low(er)-level language

If you dig deeper, though, you find some blurring between the two.

In fact an interpreter could translate the source language in a intermediate form, to speed up execution. That is what usually happens with a language that relies on a virtual machine. This naturally lead to the some questions:

Are all languages that use a virtual machine interpreted?

Are they all actually compiled?

You might say both: a language is first compiled in an intermediate form/language and then this intermediate thing is interpreted at run time. Which also lead to another issue, a compiler and an interpreter should not be thought as one program, but more of a group of programs, a system. What you, as a user, think as a compiler may actually include more than one program. For instance, it may include a linker: a program that combines different object files in one file, so that it can be more easily used. Something similar could be said of an interpreter.

Can You Tell Me Everything About Compilers & Interpreters?

So, which are all the pieces that compose a compiler or an interpreter? You could look for a precise and technical answer to such questions in academia. Or you can find discussions on these issues on StackOverflow.

What it really matters to us as developers, or even us as creators of a language, is what are the differences in working with them. Both have advantages and disadvantages, and in fact some languages can have both an interpreter and a compiler, or more than one. That is what we are going to see.

The main point still stands: an interpreter executes the code now, a compiler prepares the source code for an execution that comes later. All the practical differences descend from these different objectives.

How Do You Distribute a Program

In practical terms one important difference is that a compiler generates a stand-alone program, while an interpreted program always need the interpreter to run.

Once you have a compiled program you can run it without needing to install anything else. This simplifies distribution. On the other hand the executable work on one specific platform: different operating systems and different processors need different compiled versions. For example, a compiled C++ program might work on a computer with an x86 processor, but not one with an ARM chip. Or it could work on a Linux system, but not a Windows one.

If you are going to interpret a program you can distribute the same copy to users on different platforms. However they will need an interpreter that runs on their specific platform. You could either distribute the original source code or an intermediate form. An intuitive way to look at an interpreter is this: it is like the eval function in JavaScript. It works wherever JavaScript works, but it need a JavaScript interpreter for that platform to run.

Cross-Platform Support

This is a technical difference that leads to important real consequences: it is easier to make cross-platform programs with an interpreted programming language.

That is because, for the most part, you are just creating a program for the interpreter platform. It will be the interpreter itself that will translate it into the proper form for the real platform (e.g., Windows/Linux and x86/ARM). Of course, there are still some differences in each platform of which you must be aware. A common example is the directory separator character.

When you compile a program, instead, you need to take care yourself of all the little differences between each platform. This happens in part because compiled languages tend to be low(er) level languages, such as C++, so they give you lower access to the system and thus more responsibility. But another reason is that all the libraries you are using need themselves to support different platforms. So, if they do not support Windows, you cannot support Windows.

Speed Has Multiple Faces

Again, in the case of speed, we have a sort of paradox: a compiler is both faster and slower than an interpreter. Many people knows that a compiled program is much faster than an interpreted one, but this is not the whole picture. A compiled program is faster to run than an interpreted program, but it takes more time to compile and run a program than to just interpret it.

A compiler indeed produces faster programs. It happens fundamentally because it must analyze each statement just once, while an interpreter must analyze it each time. Furthermore, a compiler can optimize the executable code it produces. That is both because it knows exactly where it will run and also it takes time to optimize the code. Time that would make the interpretation too slow.

Runtime Speed Versus Development Speed

You might think that this is nitpicking: if you compile a program it runs faster, the time that it takes to compile does not matter. This is usually the old school opinion. And without doubt the resulting program is run more times than it is compiled. So who cares if the development takes more time? Well, for sure it takes a “bring the pain” attitude to development that is somewhat admirable. But what if the gains in run time are not relevant, while the losses in development productivity are significant?

It is one thing if you creating an operating system and another one if you making a selfie app. Even your users might prefer a not-even-noticeable loss in run time speed in exchange for getting features quicker. There is not a one-size-fits-all answer: in some contexts productivity matters more than speed, in others the reverse is true.

The Mysteries Of Debugging

There is another particular aspect that ends up being more uncertain than what one would expect: debugging. On paper debugging is easier while using a interpreter than using a compiler. That is true for several reasons:

  • with the interpreter there is one version of the executable; you don’t need a debug version for development and a release one for the end user
  • there are fewer platform specific bugs using an interpreter
  • since the interpreter transforms code on the fly, the information from the source code is still available
  • since the interpreter executes one statement at a time, it is easier to find a mistake

The Difference that Development Tools Makes

While this is all true in practice, this might be less relevant than it seems. In fact if you think about your experience, you would probably find that debugging JavaScript is harder than debugging C++. Why is that? In part is the design of the languages themselves. JavaScript uses dynamic typing, while C++ uses static typing. The latter makes easier to catch mistakes early. But in the end it comes down to the development tools. Compiling C++ by hand is hard, so most people use IDEs to develop with it. On the other hand, you can easily use text editor and command line tools to develop in JavaScript.

This means that, in practical terms, if you develop with C++, you can also debug C++. Instead you can develop with JavaScript without knowing how to do proper debugging in JavaScript.

Having said that, if we put them in the same context, each one with a great IDE and supporting tools, the situation comes back to normal. Indeed many interpreted environment are used by people that want to learn how to use a new language. It is easier to test, and find what is right and wrong, by looking at what happens line by line and in real time.

Summary

We have seen the main differences that matters between a compiler and an interpreter. More importantly we have seen that the consequences of different philosophies are more important than the technical ones. In short, there are cultures that come with certain technical choices that end up being relevant on their own. If you want speed and ease of development you are going to pick all the technologies for speed and not just one. And your users are going to follow your lead.

This is a crucial aspect to think through, especially if you want to create your own programming language. Rasmus Lerdorf created PHP to be easy to use. And indeed it was incredibly easier to use compared to the alternatives, at least at the time of its creation. But it started more as a library than as a language. And while it has improved a lot, it still suffers from its beginnings. You can still create good PHP code, but fewer of its users than usual do. Because if you just need something that works, security, maintenance, etc., all the rest come later.

If you want to know how you can practically build an interpreter or a compiler for your language, you may want to take a look at the resources to create a programming language. If you want to learn that, and all that you need to create your own language, you just need to pick a great book, loved by kids and adults alike, on how to create pragmatic, lightweight languages.