There are a lot of coding languages out there. So many, that for the blossoming computer programmer it can be a challenge to know where to start. I, like many others, came into coding through front-end development, which meant immediate and intensive exposure to the languages often used on the front-end. Often referred to as the client, in most cases this is a web browser. Languages like HTML5, CSS, and JavaScript; languages that an instructor once told me were the "holy trinity" of front-end web development, quickly became essentials in my programming repertoire.
As my coding journey has continued and evolved, I find myself increasingly curious about how these languages came to be and how they work. How did we get from machine code of 1s of 0s all the way to modern frameworks like React, to name only one example, and back again? To list all the languages that take us along that path would be far too exhaustive for the scope of this entry, so instead I would like to focus on a piece of the puzzle that has piqued my recent interest. That puzzle piece being the difference between Compile Time and Runtime. These two "phases" if you will, of program construction and execution, respectively, are unique in their own rights, but also alike in the way we take language that is more akin to human patterning of grammar, syntax, and flow, and translate it in a way that a machine can understand in order to perform a set of instructions to achieve a desired result. To start, we'll look at some of the key differences between the two, and then will use JavaScript as a vehicle to navigate between them - the syntatic Virgil to our would-be programmer Dante, so to speak.
In understanding the different environments it helps to to also understand the difference between compiled and interpreted languages, as they are often tailored to which environment or phase of program development they are operating in.
A Compiled Language is one where the source code, the code written by a human, is packaged up and "compiled" before being executed by a CPU. A Compiler, a separate program, takes the source code and converts it directly to machine code, which can then be run by the target machine. An Interpreted language on the other hand is run in real-time line-by-line by an intermediary that works to translate the source code as the program is being executed.
One of the benefits of compiled languages over interpreted ones is that all the code is vetted and checked for any errors by the compiler before being run. This also means faster execution as the code is not being translated into machine code whilst simultaneously being run. The disadvantages of a compiled language has to do with their portability as compiled code is typically written for specific target machines. That is, specific hardware architectured in a specific way. Interpreted languages on the other hand are platform agnostic, and while they may by comparison run slower than compiled code, their flexibility in implementation offers enormous advantages when programs are intended to run on a variety of machines, say through a web browser across various types of hardware.
At this point you're likely already starting to understand the difference between compile time and runtime. The former being the time in which the code takes to be compiled and the latter being the time in which in the program is being executed, or run. The next question you might ask is how any of this information is useful? And that largely has to do with one of if not THE core aspect of what it means to code -- debugging.
Modern programs are often written in a hybridization of different programming languages. Those intended to run in a client, which have to talk to programs running on server, and other programs in between. Depending on the needs of the application, as well as the preferences of the development team writing the source code, many applications are written with a combination of compiled and interpreted languages. If the program isn't operating as intended however, understanding whether the error is occurring in compile time or runtime can act as a compass in rectifying it.
Some of the more frequent errors I've run into fall under the umbrella of what's called a runtime error, also known as an "exception" and as you might suspect, is an error that occurs during the execution phase of the application. By contrast a compile time error occurs during the build of the program and most often has to do with syntax errors, like a missing semi-colon. However, compilers have a difficult time catching runtime errors, as the syntax in the code can be correct but once executed isn't working as intended, and often this can cause the entire application to crash.
As an example, one of the most common runtime errors out there is an 'Uncaught Type' error, which essentially means the program is trying to invoke a function or output a value that is either non-existent (undefined), or is incongruent with the expected value of an operation. In other words, something that isn't there or isn't expected to be there. Either way, these errors as they're detected by the interpreter in runtime, can crash the application, leaving the user dead in the water. What if instead, there was a way to handle these errors more specifically? More importantly, in a way that if an error is caught, it doesn't necessarily render the entirety of the application disabled.
It's worth pointing out the word 'Uncaught' in this type of error. What does that mean in this context? Isn't it technically caught if the system throws an error of some kind? Well, yes and no. Yes, in the sense that while the interpreter did catch this error, it wasn't told what to do with it, so it really has no choice but to crash the app. This is where JavaScript's native try/catch block comes in. Take a look at the following code:
let foo = 'foo' ;
try {
console.log(bar);
} catch (err) {
console.log('hmmmm....', err);
}
This is probably about as simple as you can write a try/catch block, but what we're essentially instructing the program to do is to 'try' to log the value of the variable 'bar' to the console. The problem is however, is that 'bar' doesn't have a value, nor has it been initialized, so what JavaScript will do in this case is proceed to perform what we've specified in the catch block of the code, which in this case is to log the string 'hmmmm' concatenated with the error. We can tell the program to do more than just log errors to the console, but the reason this is significant is that it allows more flexibility in how to handle runtime errors, and to determine if the error should block the remainder of the application from running.
This where we start getting into Blocking versus Non-Blocking code, which can be read more about on NodeJS' website, but the important thing is to understand is that there are situations in which we want, or even need, to allow JavaScript's runtime model, which utilizes an event-loop, to continue running even if certain blocks of code fail to operate as intended.
It goes without saying that JavaScript is a pretty powerful language. While not without its cadre of haters, its flexibility in implementation across both the front and back ends of web based applications secures it a seat at the roundtable of programming languages. With runtime environments like NodeJS, programmers can leverage JavaScript for sever side development using a similar syntax they would on the client side. Furthermore, with module technologies like Babel and Webpack, programmers, to a degree, can implement build phases of their JavaScript applications while maintaining JavaScript's portability across a variety of platforms. And while these are technically "transpilers" as opposed to "compilers" given they convert JavaScript to other JavaScript, being able to better delineate compile time and runtime ultimately provides developers with greater flexibility in managing things like performance and error handling.
At the end of the day though, even transpiled code still needs to be compiled to machine code in order to be executed by the target machine. And given that JavaScript is still at its core an interpreted language, what options are there for optimizing program performance similar to how compilers do for languages like C, C++, or Java? This is where the idea of Just-In-Time (JIT) compilation comes into play. Modern browsers implement JIT for JavaScript by monitoring which parts of the code are repeated, or are expected to output the same type of values (ie always returns an integer), and uses a built in compiler to optimize those operations, saving the browser from having to interpret the same line over and over again. Check out this article for more information on JIT.
In conclusion we've seen how there are different phases of programs development, which are unique enough to warrant the distinction of compile time and runtime environments respectively, the pros and cons between them, and how modern platform-agnostic programming languages like JavaScript can start to leverage the benefits of compile type languages to optimize performance. If you're an up and coming developer like myself, hopefully this helps to demystify how the languages we use to program are more human than they might seem at first. Furthermore, by exploring the differences between compile time and runtime, we better understand how code goes from our brains to the 1s and 0s that machines understand in order to perform instructed operations.
GitHub Profile: n-r-martin
Email: hello@nickmartin.design
https://finematics.com/compiled-vs-interpreted-programming-languages/
https://javascript.info/try-catch
https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/