When we look at what programmers do, they generally work with source code instead of machine level instructions which a computer’s processor runs directly. Source code is made up of what we think of as human writable instructions in languages like C, Java, Python, or JavaScript, but the processor does not run those in their native form. As a program goes from concept to running product there are many steps that may be taken which include parsing, compilation, interpretation, optimization, bytecode generation, and use of a runtime environment.
What exactly that process is will depend on the language, the compiler or interpreter, the operating system and the run time environment which may be in play. By going through these steps we can see why different languages which may appear to be very similar in what they are trying to achieve will in fact behave quite differently. Also this all helps to put into perspective what terms like compilation, interpretation, virtual machines, bytecode, and runtime optimization mean as they are all pieces of the large picture which transforms source code into something the computer will execute.

What Code does at Source Level and Running Program
Source code is what developers write and maintain. It has variables, functions, classes, expressions, control structures and other instructions which per the rules of a given programming language. What a computer processor has is low level machine instruction which is for it to perform certain tasks. The journey from source code to its execution therefore requires some mechanism which will translate or process the original instructions.
In a traditional compile workflow a compiler looks at the source code and produces machine code or some intermediate representation before the program is run. In an interpretive workflow an interpreter which runs at execution time processes the source code or an intermediate representation. Modern systems do this in a combined way which means the simple distinction between “compiled language” and “interpreted language” does not always hold in what goes on in a modern runtime.
How Compilation Works
Source code is run through a compiler and related build tools to put out machine code programs for a certain processor and operating system.
After the executable is created, the processor will run the instructions which at that point do not require the original C source code to be present during normal program execution.

The Role of the Compiler
A compiler does beyond translation of code into what the processor understands. It looks at the program’s structure and content in terms of language rules and reports out many issues before the actual run of the program. For instance a compiler will note down a wrong expression, perform type check which turns out to be inconsistent, report a missing function definition or any number of violations of language based rules. Also compilers do perform optimization of the output code they generate by which they take out what is unused, restructure what they think can be improved, better the memory access, and in general use the best machine instructions.
What this does is speed up the performance of the program or reduce its resource needs, but results may vary based on the compiler, the program being compiled, target architecture, and the set of options used. The end of the process is a development model where some issues are found out before the run time of the program and a great deal of what goes into transforming source to executable is done prior to that.
What Is Interpretation?
Interpretation which is a different method to that of execution. Rather than produce a full native executable prior to a program’s start of run, an interpreter which is the issue at hand has the charge of going over program instructions during the time of run. In past times, interpreters did at large look at source code one at a step at a time, figure out what that statement means, and then carry out the related action. Today’s interpreters are quite a bit more complex, also they do not in all cases process raw source text at all.
A runtime may first break down source code into an internal form and then go about interpreting that which is put together. Also which brings us to the case of Python as an example, in that the Python which is very popular with developers, CPython, compiles source code into byte code before a Python virtual machine goes to work on it. Thus to just say Python is interpreted is to not report on important steps which transpire between source code and run time.
Advantages and Trade-Offs of Interpretation
Interpretation provides a level of flexibility which is great because in many cases the program does not have to be turned into the native code of the target processor before it is made to run. Also this approach supports interactive development, dynamic language features, and we may see rapid experimentation along with the use of execution environments that take care of many of the runtime issues. At the same time though interpretation may at times introduce some runtime overhead in comparison to running very optimized native machine code directly. The exact performance trade off between the two will depend greatly on the language in question and also the runtime environment.
Also today’s interpreters do use caches, improved internal structures, and from profiling data they are able to do a better job at reducing that overhead. That is why we should not fall into the habit of saying things like ‘compiled programs are always fast’ or that ‘interpreted programs are always slow’. In fact the total performance of a given program will depend on the full set of implementation choices made, that is from what pre-execution work is done, what is done to optimize during the course of the program’s run, and also how well the runtime does in terms of talking to the underlying hardware.
Bytecode: A Transitional Stage.
Java source is compiled into what is known as Java bytecode which is put in class files. The bytecode is also what the Java Virtual Machine, which is also known to use the term JVM, runs. Also for CPython which is a Python implementation the use of bytecode takes place but in details and purpose it is different from Java’s. Bytecode in this regard is a middleman stage which supports portability, run time management, interpretation and also compilation.
The issue of bytecode is very much a key point which brings out that compilation and interpretation are not as much opposite as they may seem. A language implementation may compile source code into bytecode and at a later time interpret that bytecode. Also it may take frequently used bytecode and turn that into native machine code as the program is running. This is also what is to be noted that today’s language implementations are better put forward as execution pipelines as opposed to members of two very separate groups. Also the same programming language may have different implementations which use different approaches. For example one implementation may mainly interpret an intermediate representation, another may compile it out ahead of time, and another may mix interpretation with run time compilation. The language itself puts forth the rules and behavior but its implementation is what determines how those rules are turned into computer actions.

Virtual Machines and Runtime Environments
A virtual machine is software that gives an environment in which programs intended for a certain intermediate instruction set or runtime model may be carried out. Also the term does not indicate that the system will present a full scale virtual computer equal to a dedicated physical machine. Language virtual machines tend to provide services and abstractions that allow programs to run without the need to manage every hardware detail. As an example of that The Java Virtual Machine which sets up a run which is able to execute Java bytecode and also provides services related to memory management, runtime execution, and other Java platform features.
Virtual machines also improve portability which in that case the same intermediate code may run on various operating systems and architectures as long as there is a proper runtime support. This in turn changes the development approach from that of creating a separate native executable for each environment to that of putting out code which a compatible runtime will run.
Why Virtual Machines Matter
Virtual machines also see use by language designers and runtime developers to introduce features that would be hard to present in a consistent way via machine code alone. Garbage collection is an example, as is runtime inspection, dynamic loading, security, and optimization which will vary by platform. Also a trade off is that the runtime itself requires resources and puts in another layer between the application and the processor.
What we see is that a virtual machine based program may do work at start up or during execution which a native executable would not. But today’s virtual machines have it in them to greatly reduce this overhead. Also they are able to see how a program is really doing at run time which in turn presents optimization opportunities which are hard to put into play via traditional ahead of time compilation.
Just-in-Time Compilation
Some code when first run goes through an intermediate representation, while very often used code is transformed into native instructions. In many present day language runtimes JIT compilation is used which in turn allows for optimization based on which the runtime sees fit from program action.
Instead of at start up making assumptions about the code’s performance we may use runtime info to improve certain parts of the program.
How JIT Compilation Improves Execution
A JIT compiler uses at program run time different information which at the time of preparation for distribution is not available. For example the runtime may find out that a certain function is called off a few thousand times with the same types of input or that a certain code path is taken by the program in very large numbers as compared to other paths. It then uses this info to create very specific machine codes for such instances. Also the runtime may cache the compiled code which in turn means the same code doesn’t have to be recompiled each time the same operation is performed.
But there is a start-up cost to JIT which is that the runtime has to use some of the application’s resources for analysis and compilation while the application is running. This in turn means that a JIT based application may see some extra work done at start up or very early in the execution which will only later run into a very optimized state. The overall result is a function of the runtime, the work load, the hardware, and the strategy of the optimizations.

Java: Compilation in a Virtual Machine Environment.
Java’s example of how compilation, bytecode, virtual machines and runtime optimization work together. Developers write out Java source which is then by a Java compiler turned into bytecode. That bytecode is what the Java Virtual Machine runs instead of code specific to a single architecture. At start up the JVM loads in and goes through the bytecode.
Also depending on the implementation and run time, which instructions are interpreted and which are compiled into native machine code via Just In Time techniques. Thus the full Java run process includes pre — execution and in — execution compilation. Also the same Java source code may be written once and run on many supported platforms’ at the same time the JVM takes care of the hardware specific details.
Python: Source Code, Machine code, and Runtime Execution.
Python also serves as a good example in this regard which we tend to describe as an interpreted language which in fact it is not in many of its implementations. In the case of the most used CPython implementation, Python source code is parsed and turned into an intermediate byte code which is a stage in the execution. That byte code is what is run by the Python runtime. The byte code is not the native machine code which a traditional C compiler produces; it is tailored for Python’s execution environment.
This is what accounts for the fact that we say Python is an interpreted language which in practice involves a form of compilation. Also other Python implementations may use other approaches which include totally compiling Python code down to another form. Thus it is better to see the Python language separate from the implementation which runs it.
JavaScript: Present Hybrid Model.
JavaScript also provides a good case of how the classic compiled vs interpreted model breaks down. In today’s web browsers’ JavaScript engines we see many layers of code processing which aren’t present in some other languages. An engine may parse the code, create intermediate representations, run the code, collect data on code performance and use that to compile very used parts of the code. While the base principle is that some code is interpreted, in practice what we see is that the same engine may compile and interpret within the same program.
Also it is noted that which elements of a given program are compiled which are not, and how that is done, varies greatly between engines. But in general what we see is that for JavaScript which runs in such a wide range of environments from different CPUs and OS to various browser implementations and work loads — that at run time the environment can adapt the code to those specific situations very well. Also very useful.
Hybrid Execution Models
A hybrid approach in execution models is which we use many techniques at the same time instead of which we go all in ahead of time compilation or interpretation. At run time we may start with source code or byte code, interpret some instructions, monitor the run time and then turn into optimized machine code that we use most often. Also some systems may take a program and compile it into an intermediate representation and do more compile work later.
Some languages and platforms also have the choice of doing full ahead of time compilation as an option to run time compilation, that is based out of deployment requirements. Hybrid systems exist because software run time has many variables. What developers want is fast start up, efficient long run performance, portability, dynamic language features, managed memory use, and predictable deployment. No single run time technique is best for everything so modern language implementations tend to use a mix of strategies to balance those requirements out.
Ahead-of-Time vs. Just-in-Time Compilation
Ahead of time compilation and just in time (JIT) compilation see which of their processes transpire at what point in the program’s life cycle. In ahead of time compilation most of the translation and optimization takes place before the program is put out for use. This in turn reduces the time spent on run time compilation and also produces a stand alone native executable for a given target environment. JIT on the other hand puts off some of that work until the program is in run time which in turn allows the run time to use info from actual run time execution to make better optimization decisions.
The trade off is that in JIT we see that at run time we have to spend more in terms of resources for compilation and optimization. Also it is not a fact that one is universally better than the other, what works best is determined by factors like start up requirements, run time of application, hardware, portability, deployment model and the type of work load which the program is to do.
Compiled vs Interpreted
The terms “compiled language” and “interpreted language” are a good way to put forth basic concepts but should not be looked at as permanent traits which describe every instance of a language. Compilation is a process which transforms code from one form into another and interpretation is as a process which runs instructions through a runtime environment. A language may have any number of implementations which use compilation, interpretation, or a mix of both.
C is known to do ahead of time compilation into native machine code, and Java mostly uses source to bytecode compilation which is followed by JVM execution and runtime optimization. Python is thought of as an interpreted language but also does byte code generation, and modern JavaScript engines use a variety of execution techniques. Looking at the full execution picture gives a better picture than to put every language in one of two very defined categories.
Why Execution Models Matter to Programmers
Understanding what goes into turning code into software is a step for developers in making better tech decisions and to clarify issues which may at first seem mysterious. We see in compilation which is the reason a program may have to be recompiled after source code is changed. In the case of bytecode we see that some apps require a runtime environment instead of a platform specific executable. Virtual machines we note as an example of how the same app may run on many different operating systems and processors.
Also we see in JIT that a program may perform differently in the early stages of its run versus a long term work load. Interpreters we see to present which environments offer up interactive exec and dynamic behavior. Also in all of these points we note that it is also a way to better understand errors, start up time, memory use, optimization, portability and deployment requirements as at each stage from source to hardware there are different issues and tradeoffs.

Conclusion
The process of going from programming source code to a running application is in fact much more complex than the conversion of words to machine language. We see that in traditional compilation which turns source code into native machine code before the program runs, and in interpretation that which processes instructions at runtime. Also we have bytecode which is an intermediate form that is run by a virtual machine, and JIT compilation which identifies and transforms the most used code into native instructions as the program is running.
In the case of languages like C, Java, Python, and JavaScript we see different degrees of each of these techniques used which is why the black and white compiled vs interpreted distinction is very often not enough to explain today’s software systems. By looking at compilation, interpretation, bytecode, virtual machines, JIT compilation and hybrid models of execution programmers get a better picture of what goes on between the time code is written and when the software does its job on a computer.



