I’m a big programming language enthusiast, and the design of every language is so compelling to me. I love to see where languages differ, and how they inspire each other. But more than just the design, I’m also passionate about being pragmatic and getting things done. This article is about which mainstream languages I would personally recommend for production purposes, and where I’d use them.
Every language on this list is one I’ve tried, and have read about enough to have an opinion on. Some I have much more experience with, others I’ve used more passively. Your opinions may differ from mine, and that’s totally fine! By no means is this meant to be anything other than my personal opinions and experiences. This is also not an exhaustive list - there are many other languages I would recommend, too. But the focus is on what I would personally choose to use in production.
Since this is a big post, I’ve broken it up in a few sections:
My recommendations for the type of languages each type of developers should know
A breakdown of each mainstream language I’d recommend, and where I’d use them
Niche languages (a follow up post will go in depth on this!)
If you’re reading in the email from Substack, you may want to open this in the app or the browser, since it’s so long that it might not display the full post.
Subscribe if you want to get a notification for the follow up post on niche languages, too.
Let’s get into it, with my advice on how to pick a language.
Picking a language
Picking a language can be a big investment. A small project today might be a huge project tomorrow, and it’s painful to do a full rewrite from one language to another. That shouldn’t prevent you choosing less common languages, though. Just be sure to get enough experience in your potential options to fairly make an assessment.
Some questions I typically ask myself when starting a new production project:
How good is the language (solution) for what I’m trying to build (problem)?
Will I spend more time dealing with the language than getting features out?
Do the libraries I need exist already?
Will whoever inherits this from me hate me for it?
How many people will be working with me on it and do they know the language already, or are they open to learning it?
The language doesn’t need to pass all these questions, but it does make it much smoother to choose a reliable, well known language that you’re knowledgeable in. If there is a language which is missing some things I might need, I’ll often make an experimental project where I can build and test libraries or patterns.
What languages would I recommend learning?
I recommend that developers have at least a couple of languages they’re deeply familiar with, then some passing knowledge of others. Being familiar with more than one language will help diversify your skills and patterns for solving problems. While one language might currently use one particular approach, language designers or package creators often take ideas from other languages.
A developer’s strongest language should probably be the one they work with most frequently. From a career progression perspective, it should be the one that makes it easiest to find a job they love. Different people are passionate about different elements of their work. Some may be into language theory and design, so they’d go towards niche languages. Others may be into building products they see users use, so might want to go for whatever language is being used in the local job market for big products. The ideal mix for me personally, is a bit of both.
For context: I’d normally fall into the fullstack and ops category, having written a lot of backend, frontend, compilers, tooling and script code1. I’ve done some mobile development, though the majority of it has been where the UI is web-rendered.
I’ve gone into below some specific recommendations for each developer specialism I have recent (enough) experience with.
Frontend
Languages I recommend:
JavaScript (and Web APIs)
Whatever language your frontend code is in (e.g TypeScript)
html/CSS
It doesn’t matter if you use a language that compiles to JavaScript, understanding what APIs are available to different browsers, and how they work, will increase the range of solutions available to you. Knowing JavaScript doesn’t need to include things like prototypes, but focus on the more directly useful: debugging performance, interacting with the DOM, using local storage, etc. The same goes for CSS and html. While some modern frameworks abstract away from the traditional structure of CSS, or how html is created, knowing how selectors work, or what html tags are appropriate for your problem, is useful.
I’d also recommend having at least a passing understanding of the backend languages your projects use - this will make it easier for you to find answers to your questions, as well as design a project with the backend’s architecture in mind.
Backend
Languages I recommend:
One backend language (Go, Rust, TypeScript, Python)
The concepts behind SQL
An ops-specific language (e.g Bash, Python, HCL)
While you may use ORMs or NoSQL databases, understanding the query patterns from SQL databases is pretty handy. Backend developers typically get involved in infrastructure related tasks, so knowing how Bash or HCL work is pretty handy.
Fullstack
Languages I recommend:
One backend language (Go, Rust, TypeScript, Python)
One frontend language (TypeScript)
An ops-specific language (e.g Bash, Python, HCL)
My recommendation for a fullstack developer would also overlap with frontend and backend, but when it comes to languages: mastering a single backend language and a frontend language will enable a developer to work effectively with the whole stack in mind. What you want to avoid though is drifting too far from each part of the stack — fullstack developers are most useful when they’re able to solve frontend problems in a frontend way, and backend problems in a backend way. That’s not to say that there’s no overlap, though.
Being able to delve into the land of ops can be helpful too, for building with infrastructure and CI in mind.
Mobile
Languages I recommend:
The language used primarily on your target mobile devices (Swift, Kotlin)
The underlying language (e.g if you’re using Swift, Obj-C, if you’re using Kotlin, Java)
Understanding your platform’s ecosystem is very important, so you should know how to debug and deploy whatever language you’re using. Adhering to the visual standards of your platform can be pretty important, too. I’ve personally found it very useful to combine web frontend knowledge with mobile development - sometimes a solution can be prototyped quicker if you use web-rendered content. Other times, you’ll want to go fully native to ensure good performance and that platform-specific behaviours are followed.
Like frontend developers, I’d also recommend having at least a passing understanding of the backend languages your projects use - this will make it easier for you to find answers to your questions, as well as design a project with the backend’s architecture in mind.
Embedded, data science, and others
I don’t have enough recent experience with these specifically to give recommendations, but the general recommendation still applies: know a couple of languages deeply, with passing knowledge of techniques and patterns in others.
What about specific languages, though? I’ve broken these up into mainstream languages, and niches languages. Mainstream languages either have already or are seeing a lot of adoption, and would be a good choice for developers in the current ecosystem and job markets. Niche languages are those who either don’t have a lot of adoption currently, or generally aren’t used as much, but still may be worth looking at. Since this post ended up being so big, niche languages has been split off into a separate post that’ll come out next week.
Read on to find out why I specifically suggest these!
Mainstream languages
Let’s revisit those questions from above, to see how mainstream languages will typically answer them.
How good is the language (solution) for what I’m trying to build (problem)?
It’s likely that the language is ready for your specific use case.
Will I spend more time dealing with the language than getting features out?
There’s likely to be fewer weird bugs or unexpected edge cases, with lots of documentation covering how to solve them.
Do the libraries I need exist already?
The libraries you need are likely to exist.
Will whoever inherits this from me hate me for it?
The old saying “nobody ever got fired for choosing X” applies here, where X is a well-adopted, stable, language.
How many people will be working with me on it and do they know the language already, or are they open to learning it?
Many developers will be familiar with these languages.
Overall, mainstream languages are a good choice for any production project.
If you’re in a startup, mainstream languages will make it easier to get things done quickly, reducing cost, and the talent pool is wider, making scaling the team easier.
If you’re in a big company, mainstream languages reduce friction when different teams have to collaborate. Types and libraries can be shared, unfamiliar teams can contribute to other codebases, and when ownership changes, the new owners can onboard more efficiently.
I’ll also go into performance. When you think about performance, it’s important to consider two cases:
The performance of normal code, i.e unoptmized code, that most developers will be writing the majority of the time.
The performance of the hot path, i.e the most performance critical parts of the application, which may be specifically optimized to improve the overall application performance.
I’ve had a lot of experience debugging TypeScript, Python, Go, SQL and Rust performance, but not much with Kotlin, Swift, so I’ll mention it for those I have personal experience with. For some numbers and stats, the TechEmpower benchmarks and the Debian benchmark page are useful. The best data is the data that comes from your real world usage, for the same reason why integration tests often catch more real world bugs than unit tests.
Speed is important for developer tools, particularly:
If something is run frequently on every file change, it should be fast enough to not require a waiting time by the time the developer switches to their browser/terminal to check the output. If the tool is a compiler, iterative compiling (e.g only compiling the impacted files) can be a good way to get good perceived performance.
Tests should not take so long to run that developers don’t use them. They should run locally in a short amount of time. They should also match behaviours between platforms, and on CI.
Knowing which mainstream language to use can be difficult. Let’s jump right into it, with TypeScript!
TypeScript
Ease of use: 🤩🤩🤩
Activity of maintenance: 🤩🤩🤩
Stability: 🤩🤩🤩
Adopt/reconsider? Adopt
Community: Large
Job Market: Large
Use in: Frontend, backend, tooling
I’ll discuss JavaScript with TypeScript here, though they are distinct languages with separate experiences. JavaScript had a default position as a leading language as soon as browsers standardised on it as their scripting language. In the last couple of decades or so, it’s moved to the backend, where developers have seen the benefits of being able to share a codebase between frontend and backend. As this happened, people wanted to have more compiler help for writing code, and the winning way to do this was to add type safety2. TypeScript and Flow came out roughly at the same time3, 10 years ago, each backed by a big company (Microsoft for TypeScript, Facebook for Flow). Now, though, the community has more or less landed on TypeScript, and JavaScript and TypeScript have (almost) become synonyms. There are some cases where JavaScript is explicitly chosen separately to TypeScript, but they’re few and far between. By the time that TypeScript was getting popular, JavaScript developers were already using tooling like Babel to write modern JavaScript that could compile to older runtimes, so using a compiler as part of the build step wasn’t unfamiliar.
There was a space in this time for other languages, such as Elm, to provide solutions to the problems that JavaScript developers felt, but as TypeScript dominated and provided developers with a good experience, interest in the alternative languages dropped off. Why learn a new language which may not be compatible with your existing codebase, when you can just learn TypeScript and reuse your existing knowledge and code?
The dynamic between JavaScript and TypeScript is interesting. While TypeScript adds a bunch of type-related features, it doesn’t add many other language features over JavaScript. Loops, logic, functions, assignments all work the same as in JavaScript. TypeScript’s release schedules are all documented on Github, so it’s possible to keep up with what they’re adding. ECMAScript4, JavaScript’s formal definition, proposes and adopts features through community contributions. Often, once these proposals are vetted enough to most likely be adopted by ECMAScript, they get added to TypeScript. This means that TypeScript generally follows the latest ECMAScript release, but deviates when it comes to features you’d typically consider part of the type system. The deviation is usually the addition of a feature, rather than removal or modification though, which keeps TypeScript as a superset of JavaScript. TypeScript’s compiler can even support type-checking in JavaScript through the use of JSDoc comments, though the syntax for JSDoc types is a lot more awkward than just TypeScript types.
TypeScript’s frontend community is somewhat fragmented, between all the different modern frameworks. React, Angular, Vue and Svelte all see high usage. The concepts between them are mostly the same, but each framework implements them differently. As opposed to writing HTML, CSS and JavaScript as separate files and entities, these frameworks combine them together so that all the code for a specific component on your website is contained in one place, usually in declarative style. This is a difference over the old ways of writing JavaScript, where you’d write jQuery in a separate file, to modify the DOM. There was a period where there’d be a new framework or library being announced very often, too often for developers to keep up with. This seems to have settled down a bit, but I’d also point out that most backend languages have multiple frameworks, too5.
As a language, JavaScript is simple, and a mix of functional and object-oriented. The addition of classes (as well as prototypes) has helped make the object-oriented code become more approachable. It has modern features, like async/await, without deviating too much from the original language spec. This makes it an easy language to learn, without being too surprised by how it works. The lack of a broad standard library continues to be a problem, with many libraries being tiny co-dependent packages. TypeScript does broaden the language with many more features at the type level, but it’s not too crazy if you stick to the normal levels of abstraction. The small overhead in both writing code and running code makes it a good fit for functions-as-a-service. Sharing types and utility functions have enabled server-side rendering setups where the logic may start on the backend, but gets continued on the frontend.
Some particular downsides for TypeScript (and JavaScript) for me:
Complex abstract or generic types often lead to hard-to-read types combined with even-harder-to-read errors.
Types are compile-time only, but are often used as if they are active at runtime too (e.g when casting a value retrieved via JSON.parse).
Running TypeScript without a compile step requires either slow loaders on Node (e.g ts-node), or using alternative TypeScript-first runtimes like Bun or Deno.
The default tsconfig is too permissive to get the full benefits of type safety.
JavaScript generally is overused, even in cases where other languages fit better.
The standard library isn’t big enough, and the community packaging approach is to have many tiny packages rather than larger collections.
Where I’d use TypeScript over JavaScript, Elm, or ReasonML for frontend
TypeScript maps directly to JavaScript, which means it’s predictable. Frameworks you choose might change how you interact with web APIs (e.g declarative frameworks, reactive programming, vs imperative), but compared to a complete change in programming language, you’re still able to map your code closely to how browsers actually work. Higher level of abstractions (e.g Elm) make this harder, though come with the benefit of not needing to know how browsers work as much.
If you’re trying to make something unusual, performance critical, or have a short deadline, TypeScript will make all of those easier. Unusual things are easier because there’s probably other people who have done similar (or the same) things before. TypeScript is good for performance critical tasks, since it’s easier to predict the generated code and how it works. Debugging when you are two steps away from the compile target requires you to know the source language, what it compiles to, and either JavaScript or WebAssembly. By sticking to TypeScript, you can predict what JavaScript is generated trivially. If you have a short deadline, TypeScript has all the libraries you might need.
All these apply to JavaScript, too. Where TypeScript beats JavaScript is that you get the extra safety and security of types. The code is more maintainable, and easier to return to in the future. It’s also possible to make bigger refactors more comfortably. JSDoc can be an option for getting that experience while sticking to JavaScript, but the syntax for it is considerably worse than TypeScript. On the other hand, JSDoc does allow you to run your files directly in the browser, so you don’t need a compile step, with the added bonus of slightly encouraging developers to write comments. Considering the speed of modern compilers like esbuild though, I would favour the TypeScript developer experience.
Where I’d use TypeScript over other backend languages
Back when JavaScript started to be viable in production on the backend, the term “isomorphic” was thrown out a lot. A scary looking word which just means “code that can run on the frontend and the backend”, in the JavaScript world. While that initially applied to untyped code (JavaScript), I actually think sharing types is the more useful part of that. OpenAPI and other schemas can generate types for other languages, but starting with types-first often leads to better APIs in my experience. Being able to then have the client code reuse the same types as the server is quite nice, though I do recommend still parsing the data from the server - don’t trust anything if you want resilient systems. To put this into practice, you’d share type definitions but parse on the boundaries, which would either give you an error or the object matching the type definition.
The cognitive load for fullstack developers can be kept small if the frontend and the backend are the same language. Server runtimes like Node, Bun, Deno are fundamentally different from browsers, but the language and the syntax remains the same, which often means that a programming style or pattern adopted on either side of the stack can also be adapted for the other side. For example, the event model of the DOM maps pretty well to route handling. Much like handling user interactions on the frontend, backend services generally need to be async at the routing level to handle a lot of connections, so it’s a natural fit. For this reason, it’s a great fit for when a team is primarily made up of fullstack developers or mostly frontend developers. If you’re a fan of debug tooling, then you’ll enjoy the fact that your browser’s developer tools can be used to debug a Node session.
The ability to server-side render TypeScript frameworks on the backend can have good results on client-side performance and optimisations for single page apps, too. This is a compelling reason to use TypeScript as the backend-for-frontend, as a layer between your backend and your frontend.
Where backend TypeScript tends to fall down is a combination of performance, some simple problems being hard, and the small standard library.
Performance
In most cases, TypeScript frameworks fall into the middle of performance benchmarks. It is possible to optimise TypeScript for high performance, but it often comes at a cost to the developer experience. That isn’t as much the case for languages like Rust or Go, which can maintain the conceptual abstractions of the language while also delivering on the performance side. So if you want performance by default, Rust or Go might be a better option. Remember though that it may be a premature optmization.
Simple problems made hard
It’s a bit of a mixed bag with TypeScript. Let’s start with the package ecosystem. The JavaScript backend (and frontend) community mostly use npm for packages, which is simple to publish to and install into projects. However, the way imports work is different depending on how the library is put together, and for a long time, in the main runtime in use (Node), using two libraries which use the two main different import systems hasn’t really been easy.
Maintainers would need to put in quite a lot of work to make it compatible with both, and most wouldn’t do that. Some maintainers even dropped support for the old style imports completely. This means that it’s not too uncommon to start a project, but halfway find out that a dependency you’ve used for other projects is not compatible with the current one. The error messages you get given are fairly cryptic unless you’ve seen them before. The syntax also is only subtly different between the two, if you’ve only been using import. This is not the only problem with JavaScript, and it’s not even the biggest problem with the packaging ecosystem. Fortunately, other runtimes have led the way in making imports as simple as possible, and now loading both the new and old style packages has been merged to Node.
Small standard library
Some languages do have a small standard library, but often make up for it with a large community maintained library (think Boost for C++). JavaScript’s package ecosystem has led to large fragmentation along the lines of the Unix principle do one thing and do it well. I’m not 100% sure how it ended up like that, though I do think that the ambition for smaller bundle sizes prior to optimising compilers had an impact. Ironically, this principle has probably led to larger bundle sizes since what could’ve been a small self contained function now pulls in a bunch of other packages to avoid re-implementing trivial things such as checking if a value is a number or not. A fun thing to consider: if TypeScript was the default before these packages were created, then there’d be no need to implement type checks since they can (mostly) be moved to the type level instead.
What this means in practice is that projects often have extremely large dependency chains, on the scales of hundreds if not thousands. Installing and resolving these packages is slow, though great improvements have been made there in recent years. But there’s no way to vet all these dependencies, or their maintainers. As we’ve seen recently, a library with few maintainers but many dependants would be a perfect candidate for an attack. Tools like dependabot do help lessen the load of updating packages, or being aware of security issues.
Compare this to languages with a large standard library, like Python or Go, where it’s more normal to have fewer dependencies, and it could be a valid concern.
Where I’d use TypeScript for tooling
When all your code is already in TypeScript, using TypeScript for scripts or tooling can reduce the cognitive load on developers.
If TypeScript is written in a way that is aware of the platform (e.g in the browser or through Node), then it’s possible to write a tool in such a way that it will work in both places. I’ve used this for Derw’s playground, where the compiler has some feature detection to switch the implementation used between the browser and Node/Bun, so that the compiler runs in both places. While it’s possible to compile other languages to JavaScript or WebAssembly, it’s easiest if your code is already JavaScript or TypeScript. A great example of this being put into practice is the ability to run Visual Studio Code either locally or in the browser. If we ignore that the frontend of vscode is just DOM-based since it’s all Electron, we can still consider the backend (i.e the non-visual and non-interaction code) to be tooling - file manipulation and parsing are all standard practices in tooling.
My recommendation
TypeScript is probably the most suitable language for frontend web code. It has the broadest reach, the biggest community, and maps closest to the code that actually runs in the browser. Backends-for-frontends also are a great fit for TypeScript, though I would personally choose a separate language for the actual backend.
Python
Ease of use: 🤩🤩
Activity of maintenance: 🤩🤩🤩
Stability: 🤩🤩🤩
Adopt/reconsider? Adopt
Community: Large
Job Market: Large
Use in: Data science, Scripting, Backend, ML, AI
Python is a language that I find to be great to have in your back pocket. Whenever there’s a quick tool I think about making, Python is what I reach for. The standard library is huge, with popular libraries following in line by providing lots of functionality in fewer libraries. The migration from Python 2 to 3 has gone down in infamy, but since then it’s been a pretty stable language with each release adding useful new features. Much like other dynamically typed languages, the tooling situation has improved to the point where tools like Black, autopep8, and Mypy to help improve the developer experience.
Where Python does fall down though is the story around dependencies outside of the standard library, and what was once a small language (syntax and built-ins wise) growing into having many complicated features. While Python has added virtualenv support to the standard library for creating project-specific environments, actually working with dependencies is a bit chaotic. There’s pip, poetry, conda, and others. System-installed libraries complicate the story further. Many syntax changes have substantially changed the language, too. Assignment expressions may look rather un-Pythonic for those who have mainly used older releases of Python. These reasons are why I only give it 2/3 star-eyes, despite the language being very simple to understand otherwise.
That said, a shining beacon of Python is the Python Enhancement Proposal process. Ideas are proposed via mailing list, discussed, then drafted into a document over which further debate happens. This trail of documentation helps users understand how and why certain features came about, as well as how to use them. Modern Python has many great quality of life features6, and running the newer versions over early releases of 3, or the last release of 2, is a considerable quality of life improvement.
Python’s syntax has a reputation for being pseudo-code that can actually run, thanks to the preference for English words and whitespace for syntax. As a result, it’s used heavily in academia or for industries where code is a by-product of work, and not the main product. Python’s diversity and flexibility has lead to it becoming popular in many different fields, from typical web backends, to AI/ML.
A bunch of research has gone into speeding Python up, with the global interpreter lock (GIL) no longer being required in some cases, making Python a better contender for high-performance applications.
Where I’d use Python for data science
The main direct competitors to Python for data science and AI include Julia and Matlab. Both of these have interop with Python, so leveraging Python code from them is easy. The majority of models and libraries for data science are released written in Python, so unless you have attachment to Julia or Matlab, why add a layer of complexity on top of your stack? The answer is due to Python’s generalism: both Julia and Matlab are specialised for data science, and therefore have better built-ins and syntax for handling mathematics and data. On the other hand, it’s more likely that others parts of your stack may already be using Python than Julia or Matlab, so using Python will make it easier not only to interact with models, but also interact with the business logic.
Where I’d use Python for scripting and ops
Common alternative languages used for ops are Bash, perl, and JavaScript.
While Bash and perl are both extremely powerful and concise, they can be difficult to read. This factor is compounded when ops scripts are something that are modified or used rarely. Python, on the other hand, is as readable as a language can be. Some of the code to do standard operations is more complicated in Python (e.g piping a shell-builtin to another command). However, the standard library does have several options, such as os and shutils modules which provide helpers for common shell-style operations.
Bash and perl also have an advantage in that many Unix-like systems will include them by default, though making sure you use the right one and the right version is sometimes painful. For example, sh is sometimes bash, sometimes dash. Bash versions between Linux (5+) and Mac (3.2) are different by default. Different operating systems will also have different package managers and default libraries, so writing code in Python that runs cross-platform can be more efficient. Mac no longer comes with Python, so that can be an additional install - but it’s not hard to install.
Since JavaScript ends up being a defacto language for many developers, a lot of ops scripts have been written in JavaScript. Some interesting projects like zx (not to be confused with xz) and Bun’s shell have made it easier to write shell-style code in JavaScript. Python has some advantages over JavaScript, like the large standard library - it is likely that you’ll be able to do most of what you want from Python directly rather than needing 3rd party dependencies. A good example of this is the flag and argument parser provided in the standard library.
Where I’d use Python for backend
Python, like JavaScript, falls somewhere in the middle of the benchmarks when it comes to performance. Alternative runtimes like pypy are pretty fast, but not all libraries (such as the C-based ones) are able to run under pypy as CPython.
Django provides a lot of functionality for web backends out of the box, but I would personally stick to the smaller libraries like Flask or FastAPI. Python makes for a great way to set up a minimal backend, such as microservices. A big benefit to the microservices approach is that the hot path can be written in faster languages such as Go or Rust, but smaller, internal, or interactions with data science code can be written in Python.
With the introduction of mypy and Python’s type hints, maintaining large Python codebases has become considerably simpler. A distinction vs TypeScript is that all of Python’s types are available at runtime, and mypy can be used as a normal library. This means that powerful frameworks can use type information to parse user input, or have custom error messages. Python has types directly in the language specification, whereas TypeScript has to be a distinct language from JavaScript, though it’s possible to compile TypeScript without running the typechecker. A downside of Python’s approach is that types are entirely optional - running python main.py will not do any type checking. This means if you’re not running mypy manually, you might be running incorrectly typed code.
Due to the state of dependency management, it’s usually wise to run Python that other machines will also run (e.g a server, other developers) to use Docker.
My recommendation
If your role is not a software developer, Python is a great language for you. It’s simple yet powerful, it has an exhaustive standard library, and there’s great 3rd-party libraries for most things you’d want to do. It’s also heavily used in the data science and AI world, so if you’re working with those, it’s a great choice. Rewriting Bash scripts over 30 lines to Python usually helps with maintenance. If your role is a software developer, it’s also a great fit. Python web apps are great for small to medium projects with low performance requirements, though larger projects with performance concerns may need to look into alternative runtimes (like Pypy), or moving code to C.
Go
Ease of use: 🤩🤩🤩
Activity of maintenance: 🤩🤩🤩
Stability: 🤩🤩🤩
Adopt/reconsider? Adopt
Community: Medium
Job Market: Medium
Use in: Scripting, Tooling, Backend
Go fits into an interesting niche, essentially filling the role of Python for cases where Python would be too slow. The tooling story is excellent, with go-fmt, go test, go bench all providing inspiration for any modern language’s tooling. The standard library is large and powerful, and the compiler is very fast. Deploying a Go app is very simple, with all the pains of older languages vanishing as cross-compiling and statically linked binaries are supported first-class. Go is such a fast language that some of the best tooling for other languages is built in Go (e.g esbuild).
The two big changes to Go in recent years are probably their solution for generics, and the packaging system. Originally, most Go developers would re-implement a function for different data types over and over, usually only changing the types or function calls. Generics were added which solve that problem, as now a function can be made to work with arguments of many different types. This is particularly useful for collections such as lists, as prior many standard functions that could be found in functional languages (e.g map) were not supported. Go’s old dependency system would essentially vendor libraries into the project’s repo. These days, Go has introduced a module system that simplifies this.
Go’s main downside for many people is the sparseness of language features. While many other languages quickly evolve or adopt new ideas, Go’s process is long and slow. For some, it is too slow. For others, the stability and simplicity of the language is a selling point.
Where I’d use Go for backend
Go is fast, easy to deploy, and flexible.
Go fits nicely into the role of being a faster Python or JavaScript — easy and simple to write, used heavily for backend code, but with higher default performance. Writing a service in Go will usually mean it scales better with fewer resources than the Python or JavaScript equivalent, though that’s not always true. The type system isn’t as flexible as some developers would like for large systems, so deploying Go as a microservice probably makes most sense. The idea behind that recommendation is that the smaller the service is, the less likely there is need to be many complicated abstractions or types.
Go’s lightweight threads, goroutines, provide a standard way of handling concurrency as part of the language and standard library. While threading is possible in Python and JavaScript, it’s not as trivial to have both IO and CPU threading as it is in Go. That means Go is a better fit for high performance backends that do a lot of IO and CPU processing.
Unlike JavaScript, Go’s standard and 3rd party libraries tend to be more than a single function, so the dependency chain problem is less likely to occur. Deploying is relatively simple since Go apps are distributed as binaries, which can be cross compiled and statically linked. It’s possible to produce binaries for Node, Bun, Deno and Python, but none of them produce binaries as small as Go - as the beefy JavaScript/Python runtime needs to be shipped alongside the code.
Where I’d use Go for tooling
Good developer tooling needs to be simple, logical, and fast.
Simple and logical are mostly up the developer and language agnostic (as long as there’s a unix flag / argument parser), though distributing Go is a good factor into the simplicity. It’s much easier to distribute a binary than it is to distribute a JavaScript or Python package if the user does not have the right version of the runtime installed. Once a binary is produced, it can then be distributed where the users are: for example, esbuild’s package on npm downloads the right binary for the system, so JavaScript developers can use the package manager they’re most familiar with. If Python was used to write esbuild, then distributing it to JavaScript developers would involve more steps.
Tests are normally best written in the language used for the main application, though Go could be used to generate inputs for test cases. Fuzz testing is supported in the standard library for Go, so using it to generate fuzz data which is then passed to another program can be useful for end-to-end API testing. go test is Go’s built-in test runner, which makes writing tests as simple as installing Go - no need for any other packages.
Go is fast enough by default that quick-reacting developer tools are a good fit. Watching files isn’t something that is provided by the Go standard library, but since dependency distribution in Go is simple (it is inlined to the binary), then installing a 3rd party package isn’t a big problem.
My recommendation
Go is a great fit for high performance, simple applications. It’s also found a place in making tooling for other languages, such as esbuild for JavaScript.
Rust
Ease of use: 🤩
Activity of maintenance: 🤩🤩🤩
Stability: 🤩
Adopt/reconsider? Adopt
Community: Medium
Job Market: Small, but growing
Use in: Low level, Tooling, Compilers, Infrastructure
Rust is a fast-moving language which has been getting a huge amount of attention in recent years. As many legacy systems use languages vulnerable to memory leaks, like C/C++, Rust directly addresses that problem, while still being very fast. To solve the problem of memory management, Rust introduces borrows and lifetimes — essentially, explicitly making the developer tell the compiler how long different data needs to exist for. This is probably the most complicated idea in Rust, and can take a while to get your head around. The rest of Rust though will be familiar to anyone who has worked with a combination of C-style syntax and functional-style features before.
The tooling situation is pretty good, too. Much like Go, there’s a built-in way to do tests, formatting, and packages. This is a pattern you see in most modern languages, especially when compared to the older alternatives they seek to replace (i.e C in this case).
Rust is promoted as a language that could be used in all kinds of places, though in my experience, I see it best fitting for creating infrastructure, tooling, compilers, and low level native code. Web backends might fit it well too, though there is more work involved in writing Rust vs Go, for example, for comparable results.
With big organisations like the US government and Google openly calling for moving away from C/C++ to languages like Rust, no doubt it will become more popular. Large production systems, like Linux and Android, are already shipping Rust.
Where I’d use Rust for infrastructure, compilers, and tooling
I take infrastructure here to mean the systems that your user-facing services are built on. So it includes purely internal backend systems, or code that runs as a process.
Rust as a language isn’t simple, though parts of it are. What Rust does make simple though is writing high-performance low-level code that prevents typical low-level security problems for most code (with the exception of using unsafe). To write C code that doesn’t accidentally give invalid memory access, you’d have to be better at writing memory-safe C than the majority of professional C developers and never make a mistake, while Rust removes that category of vulnerability. So while C is faster than Python or JavaScript, Rust is also faster. While Python or JavaScript is less prone to memory problems than C, Rust is also less prone. It is the best of both worlds. As a result, using Rust when you need a combination of secure and fast code is a great choice.
Where Rust differs from Go is that Rust has more powerful abstractions, and benchmarks a little better. Consider the situations where you’d like to represent some complex data type, and the interactions they have. In Rust, errors can be represented by a Result type. In Go, the normal pattern is to have multiple return values, with an error that may be null. The Go version makes it easy to neglect the error, whereas using a Result forces the developer to handle the error case. Error handling is a pretty simple example, but it applies elsewhere: Rust’s workflow is more type-driven and functional, whereas Go is more classic imperative. So if you’re building something complex, Rust’s type system may make it easier to represent.
These can be summed up as memory protection built into the language, and a powerful type system, but they don’t come for free. Rust’s code is considerably more complicated than the equivalent Go, TypeScript, or Python, so microservices where you’d like to get a lot done in a short amount of time might be better in those languages.
My recommendation
If you have a mission critical system which needs to be secure and fast, Rust is probably a great fit. I would probably use Rust for the infrastructure that other things are built upon, rather than entire systems. This would include stuff like operating systems, compilers, proxy servers, internal backends, and tooling. For simple web backend services, I’d probably still go for Go/Python/TypeScript over Rust.
Swift
Ease of use: 🤩🤩
Activity of maintenance: 🤩🤩🤩
Stability: 🤩🤩
Adopt/reconsider? Adopt
Community: Medium
Job Market: Medium
Use in: App development
Objective-C is a weird language, but I like how weird it is. I wouldn’t want to work on a production project based entirely in it, though, and that’s exactly what Swift solves. Swift is a modern language, mainly targeting iOS developers, though it can be used elsewhere. With good integration into xcode, the tools that iOS developers are familiar with can be reused. Instead of Objective-C’s unusual syntax, Swift opts for a more typical C-style syntax with classes and inheritance. There’s interop between Swift and Objective-C, which means it’s easy to reuse existing codebases and libraries. Another strong aspect of Swift is that it’s under active development, since Apple have heavily invested into it.
Where I’d use Swift for mobile developer
Both old and new projects for iOS can use Swift, and probably should. It’s a modern language compared to Objective-C, and has modern language features. There’s great integration with xcode, so building apps is almost as easy as web frontends. Since Swift is being used for Apple’s internal tooling and mobile apps, there’s great support with lots of people already doing what you might want to do. For the same reasons, I wouldn’t use it for Android development unless your team is made up of only Swift developers. Kotlin has more natural Android integrations.
My recommendation
Swift is probably a better choice over Objective-C for iOS or OS X apps, and it’s already more popular than Objective-C for that purpose. I personally wouldn’t use it in other settings, though.
Kotlin
Ease of use: 🤩🤩
Activity of maintenance: 🤩🤩🤩
Stability: 🤩🤩
Adopt/reconsider? Adopt
Community: Medium
Job Market: Medium
Use in: App development, Replacing legacy Java
Much like Swift is to Objective-C, Kotlin is to Java. Kotlin helps address many of the concerns developers have with Java, while providing a way to work with Java codebases, and runs directly on the JVM. It’s the default language by now for Android mobile development, so it’s already here and established. It can target native or JavaScript platforms too, and can be used on iOS.
Many of the improvements are to do with syntax sugar, but some like type inference or nullable types actually influence how code is written, too.
Where I’d use Kotlin for mobile development
Both old and new projects for Android can use Kotlin, and probably should. It’s a modern language compared to Java, and has modern language features. There’s great integration with Android Studio, so building apps is almost as easy as web frontends. Since Kotlin is being used for Android’s internal tooling and mobile apps, there’s great support with lots of people already doing what you might want to do. For the same reasons, I wouldn’t use it for iOS development unless your team is made up of only Kotlin developers. Swift has more natural iOS integrations.
Where I’d use Kotlin to replace Java
I’d use it everywhere to replace Java. Java may have a larger hiring pool though, as it’s a long established language taught in many universities.
My recommendation
Kotlin is Google’s recommended choice over Java for Android apps, and can be used to incrementally move away from Java for backend programs. I personally wouldn’t use it in other settings, such as web frontend code, though. iOS code is probably better in Swift.
SQL
Ease of use: 🤩🤩
Activity of maintenance: 🤩🤩🤩
Stability: 🤩🤩🤩
Adopt/reconsider? Adopt
Community: Large
Job Market: Medium
Use in: Backend, Data stores
While SQL might be specific to certain databases, the concepts it introduces are important. The idea of querying data through relations is a powerful one, and it applies to logging, metrics and analytics tools too. It’s often an SQL-style language rather than directly SQL, but the ideas are the same.
If you are using a SQL database, then debugging is often related to performance rather than logical bugs. Being able to drop down from your ORM to an SQL query directly can help optimise the hot path.
My recommendation
If you’re doing backend data storage code, learn SQL, and the ideas behind it. You might prefer a NoSQL database, but the concepts behind queries and making scalable safe transactions (e.g ACID) apply to many situations.
Niche languages
Niche languages have the same considerations to take into place, but keep in mind the answers to them are probably all going to be more negative.
How good is the language (solution) for what I’m trying to build (problem)?
It’s more likely that the language isn’t ready for your specific use case.
Will I spend more time dealing with the language than getting features out?
There’s likely to be more weird bugs or unexpected edge cases.
Do the libraries I need exist already?
The libraries you need are less likely to exist.
Will whoever inherits this from me hate me for it?
Fewer people will be familiar with the language, fewer still with the unusual tricks you had to do to solve problems due to language or library limitations.
How many people will be working with me on it and do they know the language already, or are they open to learning it?
Learning a niche language isn’t always on someone’s to-do list.
Does that mean that you shouldn’t use a niche language? No! But you should invest more time in preparing before starting on a project. Conduct mini experiments with smaller projects to identify the hardest problems. Build libraries that are missing. Check if your team is onboard. Figure out the exit plan if the language turns out to not suit the problem. If it all looks too problematic, stick to a mainstream language.
If you adopt a niche language at the right point, you might be able to ride the wave as it becomes better and more widely adopted. On the other hand, it’s entirely feasible that the wave you were ridding disappears, and you’ll crash into the sand.
Since this post is so long, in-depth niche language coverage will be in a later post.
Conclusion
When you’re starting a new project, evaluate the language you choose in the context of what you’re making, who it is for, and what you need to solve the big problems you expect to face. Spend time learning the community, or making small prototypes which can be thrown away. Talk to people on your team or your company to understand what they value, and make a joint decision.
Try to learn at least a couple of useful languages for your role. Have one language which is your “comfort zone”, and another which challenges you.
I hope you found this post interesting! If so, feel free to sign up for more - including the follow up post to this one, where I’ll go into more niche languages like Elixir, Elm, Lua, and Derw.
For a more philosophical read on my love of programming languages, check out this post on the Derw blog.
There’s also the Closure Compiler, by Google, which would give errors as a by-product of static analysis for performance reasons.
One little trick I use: go to the LICENSE file on a Github repo, and look at the history. Usually it is in the repo from the start, and rarely has had too many changes. It’s not always the case but it works most of the time.
ECMAScript’s working group, TC39, is composed of experts from many different companies and academia, and their meeting notes and agendas are all openly available.
Though please, no more 3 line implementations of isEven that depends on isOdd that depends on isNumber.
Simplest example: f-strings.