DEV Community

Cover image for Go for the JavaScript Developer: What Tripped Me Up and What Finally Made Sense
Frihk Ian
Frihk Ian

Posted on AI-assisted

Go for the JavaScript Developer: What Tripped Me Up and What Finally Made Sense

I was really offended when Go first refused to compile my code because of a single unused variable. In JavaScript it's perfectly possible to have a dozen such variables without anyone noticing, but Go focused on one and basically told me to clean my room. Even though I've now been using both languages for a long time, I can state that the offence I felt at the beginning has changed into respect. If you're using JavaScript and are wondering whether Go is worth your time, then this is the article I wish I had read at the start. I'll explain the areas in which I had the most difficulty: errors, concurrency, types, and tooling.

Two Languages, Two Personalities

If you imagine JavaScript as someone who always agrees to requests, then it developed in a browser environment where flexibility was valued more than strictness and so will do its best to make sense of almost anything you give it. Go, on the other hand, is the friend who wants to know whether you've really thought something through; it was developed at Google by people who had grown tired of slow builds and large codebases, and its aim is to have small, boring, readable code that a stranger could understand even on a Monday morning. Neither of these two friends is better. But if you expect Go to offer the same degree of freedom as JavaScript, you'll end up arguing with the compiler during your first week; however, if you realise that it is trying to protect you, those arguments will disappear a lot sooner.

Errors: Thrown Versus Returned

This was the most important mental shift for me, so permit me to make it clear. In JavaScript, errors are raised and caught at a higher level, which means a function can fail without its signature providing a warning.

try {
  const data = JSON.parse(input);
  use(data);
} catch (err) {
  console.error(err);
}
Enter fullscreen mode Exit fullscreen mode

In the programming language Go, errors are treated as ordinary return values rather than exceptional events, so a function will explicitly return both its result and an error. This requires the developer to handle potential errors directly at the location where they may occur, as shown in the accompanying code example. For instance, after attempting to parse input using json.Unmarshal, the Go function immediately checks if an error was returned and deals with it before proceeding; this explicit pattern contrasts with JavaScript’s approach, where error handling is decoupled from the function’s interface.

var data Config
if err := json.Unmarshal(input, &data); err != nil {
    return fmt.Errorf("parsing config: %w", err)
}
use(data)
Enter fullscreen mode Exit fullscreen mode

Allow me to be straightforward with you: at first, err != nil did do seem like never-ending repetition. However, I then realised what advantage it was giving me. Whenever the program could fail, that fact was clearly shown right on the page, and you couldn't ignore a failure path without the code looking obviously incomplete. The more you added code, the fewer nasty surprises you had at three in the morning.

Concurrency: One Thread Versus Many

Here's a fun method of illustrating the difference. JavaScript makes use of a single thread and an event loop. In the case of using async and await, no processing takes place in parallel. What you're actually doing is asking the runtime to carry out other tasks while it is waiting for something slow, such as a network call. The approach is simple, and as a result you rarely have to concern yourself with two bits of code accessing the same data at the same time.

Go takes a distinct approach to concurrency: goroutines are lightweight threads managed by the Go runtime and can execute tasks concurrently, utilizing multiple CPU cores when available. You initiate a new goroutine by prefixing a function call with the keyword go, which instructs the runtime to schedule that function for asynchronous, potentially parallel execution.

go func() {
    result <- fetch(url)
}()
value := <-result
Enter fullscreen mode Exit fullscreen mode

The power is genuine, and it brings with it a number of problems which you should be aware of. Since goroutines share memory, it is possible to encounter data races in which two of them read from and write to the same variable without coordinating, and deadlocks in which they wait for each other indefinitely. I have discussed both of these issues after having come across them directly. Although Go provides you with good tools such as channels, mutexes, and the race detector, it won't prevent you from using them incorrectly. JavaScript's runtime protects you from all the kinds of bugs involved here, whereas in Go that protection is up to you.

Types: Suggestion Versus Contract

If you're already using TypeScript, static typing won't intimidate you; in contrast, TypeScript's types disappear at runtime and can be ignored using any, whereas in Go the types constitute a contract that is enforced by the compiler. There's also no ordinary undefined or null value to run into, since every type has a zero value—an unset integer is 0, an unset string is "", and an unset boolean is false. This eliminates the entire class of "cannot read property of undefined" errors. At the same time, it raises a subtle new issue regarding how to distinguish between 'the user sent zero' and 'the user sent nothing', and from that point on pointers and the omitempty tag on JSON fields prove to be useful.

The way interfaces function is also interesting, and that's my favourite aspect. A Go type automatically satisfies an interface whenever it has the appropriate methods, without any need for an implements keyword. It does feel a bit strange at first, but then you realise that you can define a very small interface exactly where you're using something and substitute a dummy one when testing, which in turn makes your code extremely easy to test.

Tooling: The Stuff You Feel Every Day

It was here that Go made me accept it. There's just one formatter, gofmt, and everybody uses it, so there are no disagreements about code style. The standard library is so thorough that you can create a fully functioning HTTP server using just net/http and nothing else, whereas the JavaScript version typically starts by picking a framework and then installing a dozen packages. Testing and benchmarking are included, and the final program is a single binary that you can copy to a server, without having to install a runtime and without needing to drag along a node_modules folder. If you care about deployment, as I am becoming more and more concerned about doing, then this level of simplicity is difficult to exaggerate.

What I Still Love About JavaScript

Don't imagine that this is a letter announcing a breakup because it isn't. JavaScript remains my default choice whenever I'm working with the browser, and the environment it offers for creating interfaces has no equal, which is the reason I continue to turn to React and Next.js. With Go, the verbose syntax can become tedious when all you want to do is quickly prototype something, and working with loosely defined data is more difficult than it is in a language where an object can hold any type of value. The real question to ask is never "which language is better?" but rather "which trade-offs suit this job?"

Why I Use Both

The system I'm using at the moment has Go on the back end and JS on the front end. Go is used in the areas where it's important to have correctness, good performance, and easy deployment, while JavaScript is employed for the parts that users actually see. There's a benefit that I didn't anticipate: by expressing the same concept in two different languages based on different philosophies, you are compelled to understand the idea itself rather than merely the syntax. If you're confined to a single language, switching to the way of thinking from another language is one of the quickest methods for improving your skills.

The Bottom Line

If you're a JavaScript developer who's interested in Go, you can look forward to being annoyed for a week before coming to appreciate it after a year. You should learn to accept returned errors, show respect for goroutines before you start to trust them, and let the compiler contradict you rather than opposing it. As a result, your code will become more explicit and you'll place greater trust in it. Since I myself am still investigating the more advanced aspects of Go, please tell me in the comments which part caused the most difficulty for you, and I'll write about that next.

Top comments (0)