Grafvision - Fotolia
At a high level, the difference between a compiled and interpreted language is that an interpreted language is compiled into an intermediary form and not machine code. Compiled code can run faster, but, unlike interpreted code in Java, it is not platform agnostic.
The code written in a compiled language is converted directly into machine code that is specific to the targeted runtime architecture. Interpreted code is compiled into an intermediary that runs on any architecture.
But this clear distinction tends to fade when you examine the exact features and potential capabilities of any individual programming language.
Examples and benefits of compiled languages
Rust, Go and C++ are popular compiled languages. When applications written in these languages are packaged for deployment, the source code is converted into machine code. The applications are then deployed as executable files that will run only on a single, specifically targeted architecture.
Programs compiled into machine code have a speed advantage over interpreted languages, as there is no intermediary step required before instructions execute on the processor.
The speed advantage of the compiled language Golang (Go) in comparison to an interpreted language such as Java is one of the reasons why organizations write their microservices in Go. In cloud computing environments, where users get charged for every clock cycle, it makes sense to use the most efficient deployment artifact. Unsurprisingly, the Docker container is written in the compiled language Go.
The drawback to a compiled language is that the deployment artifact is architecture specific. A C++ application written to run a Windows-based, x86 architecture, for example, cannot be installed on an x64 Ubuntu machine. Applications developed with compiled programming languages are not cross-platform capable.
Benefits and examples of interpreted languages
In contrast to compiled languages, interpreted languages generate an intermediary instruction set that is not recognizable as source code. The intermediary is not architecture specific as machine code, either. The Java language calls this intermediary form bytecode.
This intermediary deployment artifact is platform agnostic, which means it can run anywhere. But one caveat is that each runtime environment needs to have a preinstalled interpreter. The interpreter converts the intermediary code into machine code at runtime.
The Java virtual machine (JVM) is the required interpreter that must be installed in any target environment in order for applications packaged and deployed as bytecode to run.
The benefit of applications built with an interpreted language is that they can run on any environment. In fact, one of the mantras of the Java language when it was first released was "Write once, run anywhere," as Java apps were not tied to any one OS or architecture.
The drawback to an interpreted language is that the interpretation step consumes additional clock cycles, especially in comparison to applications packaged and deployed as machine code.
The need for bytecode to be interpreted on a JVM before machine instructions can be fed to the CPU is often given as a reason why a Java application might be performing poorly, although the validity of such "Java is slow" claims is questionable. Poor JDK performance is often attributable to more mundane factors such as memory leaks or poorly optimized garbage collectors, not factors related to the optimization of the underlying JDK. But bytecode compilation is a performance factor that can't always be dismissed.
The blurred interpreted vs. compiled line
The further you push the distinction between compiled and interpreted languages, the more the boundaries blur.
Some might argue that Java is actually a compiled language, as source code gets compiled into bytecode, which is a valid point. Furthermore, the JVM and the just-in-time compiler perform so many optimizations that machine code is often already generated and cached when it's invoked, which means there is no actual interpretation going on at runtime. The Android Java APIs are never compiled into bytecode.
Similarly, there is nothing preventing a C++ program that runs on a cross-platform interpreter such as GraalVM from generating intermediary code. There are plenty of fourth-year, computer science projects that do exactly that. So, it's somewhat unfair to permanently pigeonhole a programming language as a purely compiled one.