I had been writing .NET backend systems for more than 4 years. I've used it at work, side projects, and college projects.
Recently, I decided to use go to build the backend for a mobile app. I value simplicity, and therefore enjoyed go the moment I stepped through go by example
Go prioritizes simplicity and intent. It is imperative and eliminates many complexities that haunt other languages by choosing a small subset of features. If you're coming from C# or java, you may feel confined by this minimal language at first.
But you will quickly realize that go's strength comes from its simplicity.
Things I like
No need for external dependencies
Everything works out of the box. You can build an entire backend with middlewares, services, repositories, database connectors, etc. without ever looking beyond the standard libraries go provides.
In contrast, building a similar system in C# requires external dependencies.
Go is not opinionated
There is no right way of doing things in go. For example, if you want dependency injection, you set it up yourself and pass your services as parameters. Go does not do any DI magic under the hood which makes you mindful of how you create and pass your objects.
Go offers more freedom than C# which can lead a new developer to stumble until they find what works best for them; but I see this as an advantage. It forces you to make deliberate choices about the structure and flow of your code rather than blindly conforming to a predefined structure others have placed for you.
.NET's minimal APIs are the closest you can get to Go in a C# backend. It's a step in the right direction.
Built in testing
When testing in C#, I would usually use an external library like xUnit and another for mocking. In go, testing is built in. You don't need to rely on any 3rd parties to test. You can benchmark, setup before tests, and teardown after tests using the built in testing library.
Less Boilerplate
It takes less code to get a server up and running than C# .NET or java spring. This is a hello world http server in Go:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", HelloServer)
http.ListenAndServe(":8080", nil)
}
func HelloServer(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
I know a minimal C# API is just as small if not smaller, but most backend C# systems use controllers. A typical C# codebase will likely be much larger and contain more boilerplate compared to go.
Extremely Easy to Learn
You can learn go in a few days and build something with it in no time. The learning curve is far from steep. You just hop in and next thing you know, you're spinning up services in no time. This is also great for hiring: it's easy to get a junior dev on the team and they will be contributing soon after.
Concurrency
Concurrency in C# is complex, especially for new developers. There are many ways to achieve concurrency but equally many caveats and catch22s. You can use Parallel.invoke, Parallel.forEach, Task.WhenAll, Task.WaitAll, and more. With these abstractions, you gain granular control over the task: you can control if it's blocking, configure cancellation, etc. But you also risk running into countless complications. Be careful if you don't want the bloodcurdling deadlock monster entrapping you. example
In go, concurrency is straightforward and untroublesome. you simply use the keyword go
to run a function asynchronously. For more control, you can use channels and waitgroups which go provides to allow for fine grained control. Go's goroutines are similar to C# tasks where multiple goroutines map to a small number of OS threads. One of the differences is goroutines contain normal go code while C# tasks need to be written (or potentially re-written) to be asynchronous.
Things I Dislike
Lack of Mature Generics
Generics were recently added to golang but they are not mature. You cannot use generics without instantiation which limits your options. Functionalities that are easily achievable with generics in other languages may require workarounds in go.
Another pertinent issue is the lack of support for union types on struct fields. See this issue.
Lack of Built-in Higher-order functions
Go does not have higher-order functions out of the box. If you're used to using LINQ in C# or streams in Java - which use lazy iterators to filter and process data immutably - you might be disappointed. Technically, you could write functions like these in go using closures. In most cases, the performance and complexity overhead of these custom iterators outweigh the benefits. This is by design. Go is meant to be simple. But I still find myself missing the niceties of LINQ from time to time.
Closing Thoughts
If you noticed, I did not compare performance because it doesn't really matter in this context. You can build very capable and performant systems with C#, go, java, rust, etc. Your performance bottleneck will almost never be one of these languages, but rather your design choices.
I also refrained from complaining about Go's error handling because, although it's not as good as Rust's or C#'s, it's still fine and forces the developer be mindful about handling the errors.
If you haven't already, give golang a try.
None of this really matters; just build something
You can sit here all day arguing about syntax differences and language superiority, but it really doesn't matter. The best programming language is the one you're most productive with. So just go out there and build something. Build it with go, java, or even assembly. Just avoid idle hands.
Top comments (4)
About concurrency: Most app rarely need any "Parallel.xxx" or "go xxx". Most of time we only need Async for I/O operation. in Go it is often by default, you don't need to create a go routine for I/O operation (in most case). In .net, it is trivial to achieve async I/O with async, await. Real concurrency is always hard to do correctly even with Go (checkout the Go's conc package)
About boilerplates codes: the Go language has simpler syntax, less sugar/magic codes, you usually need much of boilerplates Golang's codes to achieve the same thing in C#. In other words, Go's code are usually more verbose than C# codes. The most Go's "boilerplates" is obviously the indispensable "if err != nil" everywhere.
About eco-system: The Golang's eco is still young. You often don't have much choice for the things you need, if it was something uncommon or not very popular, it might become real blocker! On the other hand you basically can find anything in the .Net eco-system. You are likely concentrate on developing your application rather than becoming another library author because of some missing (or unsatisfied) libs.
It's clear that you value Go for its simplicity and encouragement of conscious design choices. Your comments about Go's minimalism and lack of dependency on external libraries resonate strongly with developers who value lightweight and efficient workflows. Features such as inline testing and simple parallelism are really a game changer for those who are tired of overly complex systems. Just like this danlarremore.com/5352/ service makes learning much easier. It has everything about Network Analysis and Modeling.However, your observations about Go's limitations, such as the immature state of generalizations and the lack of higher-order functions, point to areas where Go's simplicity can seem limiting. For developers coming from C# or Java, this can be a significant adjustment. Your suggestion to “just build something” is a great conclusion - it's a reminder that productivity and comfort often outweigh debates about a particular language.
Well written article. Only critique is that I have found I need to use more external dependencies in go rather than dotnet.
DI is simple. Why you said it is magic?