I see many questions, especially from those trying to get into the field or early in their career, centered around finding and using the “best” programming language. I feel it is needed to level-set the question by finding the “best” programming language’s misnomers.
Firstly, there is and never will be the absolute best programming language due to the nature, diversity, and innovation in the software itself. The industry creates programming languages faster than the mainstream can keep up. Now, big software anchors languages around legacy software. New grads enter the market with spiffy new languages and ideas that create a constant churn of programming languages over time. In essence, I see this as a perpetual cycle of technology churn that is the business’s nature.
What is a programming language?
A programming language at its core is a tool to be used to create software. It is a specific syntax that provides features and value to write maintainable software for a purpose. If existing languages are not optimal for the business domain you are trying to develop; perhaps a new language has room to be born. Over the last number of years, computer programming languages have evolved significantly to automate and simplify complexity. Ultimately, enabling more complex and diverse software to get up and running quicker than ever today.
Programming languages themselves are products, and the end-users are developers. The languages adapt and try to implement features, paradigm support, compilers, interpreters, and syntactic sugar to make the language more competitive. They try to attract third parties to build into the ecosystem and publish shareable code modules that fulfill a need. It’s every programming language’s dream (developers too) to be the one language to dominate the market with a consistent developer experience. However, the tech field’s free market, in general, doesn’t allow one language to seize it all. As the adage goes, “a jack of all trades is a master of none.”
What types of programming languages are there?
Programming languages are a competitive field in their own right and have grown since the days punch cards were used.
There is another interesting evolution that goes hand in hand with programming languages, programming paradigms. However, I will not explore those in good detail in this article as they are a lengthy subject on their own. Let’s take a look at the general categories I see programming languages bucketed into.
Machine language
Machines or rather processors read 0s and 1s sequenced together and perform elementary operations on those sequences at the machine’s heart. These sequences are machine code, and it is nearly unreadable to humans. Developing a machine code program would likely take eons due to the nature of being near unreadable. Our first order of business is to create a more human-readable language so we can actually speak to the machine. Also worth mentioning, machine code is unique to a specific CPU architecture.
Assembly language
Here we are, the lowest level language most humans attempt to write software in these days. Assembly has minimal overhead introduced between the machine, and the assembly language compiles to machine code. This makes assembly code hypothetically the definitive way to make the fastest software (not factoring in optimizations compilers can do to higher-level language). Why are we not all using assembly then?
Assembly is a very low-level language that gives you the finest control possible over memory registers, moving blocks of memory and doing basic math on those memory blocks with some basic logical conditioning. While it may be possible to build some fancy programs with assembly, the complex math-based logic and lack of varied programming paradigm support (assembly is only imperative) would leave a maintenance nightmare, readability, and a total memory and math disaster. Worse yet, assembly languages are coupled to a specific CPU architecture. This means you will have to re-write your software to support a different CPU architecture.
Compiled language
Compiled languages really draw the line above the low-level assembly languages. We enter the “high-level languages.” These high-level languages now introduce additional software in between your code and the machine code. However, additional software provides you with a lot of extra bells and whistles from run time exceptions, diverse programming paradigms (such as OO and functional). Best of all, a compiler can compile down to any machine CPU Architecture that the compiler supports.
Heck, a compiler can really compile your code into anything, really, even other languages. A compiler that compiles a superset language like Typescript down to Javascript is called a “transpiler”. Compilers also provide the capability of the “build step.” This introduces several extra automated steps to check your code for type errors, logic errors, linting, and other inferences for a higher-level programming language. Another big advantage of compilers is that they can often optimize your code as it compiles down to a lower-level language automatically.
Fancy compilers like LLVM are even compiling to a high-level assembly language that can be used for web interpreters and can support a wide variety of languages. Compilers add the capability to compile virtually anything to anything. It is an additional software piece that someone needs to write and meet any given programming language syntax. All programming languages can have a compiler built for them, given the community’s will to build, support, and add features to make them competitive.
Interpreted language
Interpreters, very similar to compilers, are the new era of development. They actually run your code in real-time, even as you type. This allows for a diverse and rich development environment allowing programmers to no longer need to “compile first” and code on the fly. They support rich runtime errors, memory safety, and many bells and whistles the compilers offer during the runtime. However, since there is no “build” step, you don’t get rich typing errors or compile-time breaks to quickly catch certain logic errors.
Interpreters expose rich debug experiences for a developer, leading to fairly quick access to understanding what’s happening in the code. You can do this without interpreters, but interpreters do well to enhance tooling and diagnostics since they directly run the code.
Because interpreters need to process the language at run-time, they have a huge setback. They are quite slow and leave all the optimizations up to the developer. Interpreters can also be embedded into other programs allowing platforms for multi-tiered software systems. For example, it can be possible to run a C++ program with a Javascript interpreter built-in.
Hybrids
It goes without saying that programming languages enable developers to achieve more. As languages get more competitive, they often adopt features that make other languages have an edge. We see Java and C# introduce “virtual machines,” which are really just interpreters. Interestingly, these languages also have compilers. They steal the best advantages from compilers and interpreters by having an interpreter capable of running in multiple environments. While still being compiled down to a more compact and consumable “byte code” that is as close as an interpreter will get to machine code. The byte code then runs on the interpreter instead of the uncompiled code directly.
Further, numerous programming languages that have traditionally been interpreted now have “beefed” up built-in compilers! The interpreter is taking the programming language and compiling it on the fly! This is “Just In Time” compilation (JIT). Often also applying optimizations to the compiled code to make it run faster.
This optimization has a negative impact, though. The original programming language code and compiled code that the interpreter created on the fly takes up vast memory as overhead. It is the nature of the beast, though. While JIT compilers offer substantial speed boosts to traditional interpreters, they consume memory like no tomorrow.
What is the best language(s)?
This section will not age well over time as it certainly will fluctuate year to year. Considering everything mentioned above, programming languages are ever-evolving on a competitive market and strive for shares of the space as any other consumer product does. Here is a stab at categorizing several in-demand topics below and selecting the best languages to be used for each.
Considerations
What should you consider when evaluating programming languages as the best for your work? For starters, I’d recommend the highest-level language you can use. For high compute, graphics processing, video, audio streams, operating systems, or drivers, you can consider a nonmemory managed language such as C++ or Rust. This will ultimately require more development time, though. Thus, you can use a hybrid approach and consider wrapping the compute-intensive code as an underlying engine and user an interpreted language on top of it to leverage the best from all worlds.
The speed of a language is often a non-concern. Your algorithms will likely bottleneck before the language does. However, certain intensive computations and serialization may lead to bottlenecking on interpreted languages only in the most niche of scenarios. Another consideration is what does the language not have that might lead you to spend quite some time developing a custom solution for that other languages may offer?
Some features to consider in a programming language that may be useful to your project.
- It is strongly typed that helps with long term code maintainability
- Supports programming paradigms that help you architect and shape the code in a maintainable and readable manner
- Libraries, frameworks, or ecosystems to help get the infrastructure up and running faster suited for your needs
- Memory Management
- Compilers that are robust and optimize well for your target platform
- Multi-threading (could be a blessing and a curse)
- Actively supported interpreters that run in your target environments.
- Package management systems that fuel and grow an open-source community that has solutions for your target business needs
For data science, machine learning, and engineering
Python has a vast ecosystem in open source libraries, notebook prototyping, data exploration tooling around dealing with data. Whether you need to aggregate data engineering data, make a recommendation engine using machine learning, or perform deep data statistical analysis. Python does the job and scales well enough. Note that Python is commonly used as an interpreted language.
For graphics, operating systems, game development, and hardware
C++ is still the king of being a systems engineering language. It provides a versatile and powerful toolset for integrating with various low-level driver libraries, hardware command sets, and being a segway to higher-level software. I believe it’s still the go-to language if you need direct hardware control, including CPU, GPUs, and access to specific memory blocks and APIs from the hardware. With great power comes great responsibility, and C++ certainly gives you plenty to blow your project to smithereens. I only recommend C++ for low-level needs; otherwise, other languages do better at application level programming (maintainability and ease of development). The performance gains are usually not worth the engineering costs for maintaining a C++ project.
For web stack front ends
Javascript is about as close as they come to achieving the dream of “run a program anywhere.” This is thanks to the rich ecosystem of interpreter engines being handy on every major OS and embeddable on any application. Without a doubt, Javascript is most known for its dominance on the web front end market. There has been a lot of investment (for example, Google’s V8 Javascript engine) to make Javascript as fast as possible. However, it is still commonly an interpreted language, which means it is hungry for memory and pays the interpretation overhead.
Typescript is a superset language that I highly recommend over Javascript (it transpiles down to Javascript). Especially for larger projects needing to be maintained over time. It does require you to start building your project as part of the development cycle (Typescript transpiles to Javascript). Still, it pays its weight in gold for any project where you need to work with other developers. It adds “typing” to the Javascript language, which helps readability and maintainability.
For interoperability of language and compute intensive front end
Further, with the modern support for web assembly, I can also recommend Rust. It is complementary to Javascript/Typescript for special case needs on the front end. Particularly where your project needs to integrate with lower-level libraries written in other languages. Further, Rust will compile down to a much smaller code bundle (a web assembly language LLVM), which leads to performance optimizations in code delivery. The fewer bytes transferred over the internet, the faster the code can execute. Since it won’t need an interpreter, it has a fair amount of performance potential for computationally heavy processing such as video, sound, simulation, and other heavy calculations. Be aware, though, that serializing data to and from the web assembly layer has a cost as well. Transferring large data from and to the web assembly layer can kill potential performance gains.
For web stack back ends
This category is definitely more of a scattershot, and I feel is more competitive than the others to draw just one winner. So I will recommend several that have all been successful in building strong web backend ecosystems.
Java and C# are the typical winners and so competitive that it could just boil down to a matter of taste. Java’s ecosystem mostly revolves around Spring Framework. Spring is a solid and successful platform for building web services. Some do not like Spring as it relies heavily on annotation decorations. C# has its own ASP.net platform built-in and a decent community-driven ecosystem around it. Both Java and C# are interpreted, but the languages compile down an intermediary language beforehand (byte code). This makes them more portable to run on different machines while still having compact code that runs closer to the machine.
Go is a newer language that is gaining more fame. It is a compiled language but offers similar benefits to Java and C# in terms of garbage collection, high-level libraries, strong typing, growing open source community, and memory safety.
Noteworthy runner ups
Javascript and Python are also trying to take some of the market shares and worth an honorary mention. They can be just as effective (especially as a microservice) as the other mentioned above. Python and Javascript are normally interpreted while the rest are compiled, which will hurt performance for computationally intensive tasks. While both have strong open source communities, I find the libraries’ quality and support around the back end to be less quality than the ones above. It is still growing, so that may very well change.
For native and mobile development
Native development often implies that an OS has built a particular framework with sometimes opinionated APIs around what an app can be with limited access to OS and hardware calls. These languages tend to be very coupled with their host OS but often provide a rich developer compiler and a feature-laden IDE toolset to make development pleasant. Often you will code for a native environment; you want a more “rich” experience for a front-end client that the web does not give them.
Almost all native development will be front end client work (just not with the web stack explicitly). Web languages are interpreted, while native languages are compiled and optimized, giving more performance to the platform’s user.
The primary popular native development platforms that I can think of are Mac OS, iOS, Android, and Windows. Mac OS and iOS use Swift, a good language and a huge improvement over their prior Objective C language. Windows uses C#, and a particular framework called the “Universal Windows App” (UWP). Android uses Java or Kotlin, which both have great support and community around them.
Native hybrid frameworks
There are also new “hybrids” popping up offering near-native development experience for developers and end-users, as I mentioned above. Of course, a jack of all trades will be a master of none. However, writing several native feature parity applications is very costly, and alternatives may be worth the reduced cost. One such hybrid framework is Flutter, which allows you to write for a native Android program, iOS, Linux, and Windows.
There you have it! A fairly comprehensive level setting of what a programming language is and what it provides the developer. Remember that languages are tools. I advise you not to “love” a language but to treat it humbly as a tool to do your work. Weigh the language you decide to use for any project with pros and cons. There likely will never be one language to rule them all, and you may very well be using a language that will hinder your project.
If you enjoyed this article, I recommend checking out more of the differences between engineering and coding when it comes to building software.