Go for TypeScript Developers: What to Expect
Go for TypeScript Developers: What to Expect
I spent years writing TypeScript — both on the frontend with React and on the backend with NestJS. When I started learning Go, I expected it to feel like a step backward. Statically typed, compiled, no generics for the longest time. Turns out, I was wrong about most of that.
What Feels Familiar
The type system will feel comfortable immediately. Go is statically typed, and the compiler catches a lot of the same errors TypeScript does. Interfaces work similarly — you define a contract, and any type that satisfies the contract implicitly implements it. No implements keyword needed.
// Go
type Writer interface {
Write(data []byte) (int, error)
}
// TypeScript equivalent
interface Writer {
write(data: Uint8Array): number;
}
Structs are Go's version of types/interfaces for data shapes:
type User struct {
ID uint `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
If you've used TypeScript interfaces to shape API responses, this will feel natural.
What's Different
No exceptions — errors are values
This was the biggest mindset shift. In TypeScript you throw errors and catch them. In Go, functions return errors as plain values:
user, err := getUserByID(id)
if err != nil {
return nil, fmt.Errorf("fetching user: %w", err)
}
At first this felt verbose. After a while, it clicked — you always know exactly what can fail and where. No surprises from uncaught exceptions deep in the call stack.
Goroutines vs Promises
TypeScript has async/await built on top of JavaScript's event loop. Go has goroutines — lightweight threads managed by the Go runtime. Concurrency is a first-class citizen, not an afterthought.
// Spin up a goroutine
go func() {
processImage(imageID)
}()
Channels let goroutines communicate safely:
results := make(chan string, 10)
go func() {
results <- processImage(imageID)
}()
result := <-results
No null — but there's nil
TypeScript's strictNullChecks trains you to handle undefined and null. Go has nil, but only for pointers, interfaces, maps, slices, and channels. Value types (int, string, bool, struct) have zero values instead of null.
var name string // "" not nil
var count int // 0 not nil
var user *User // nil pointer
What I Still Miss
- Generics are improving — Go added generics in 1.18, but the ecosystem hasn't fully adopted them yet, so you see a lot of
interface{}in older code. - No
map/filter/reduceon slices — you write explicit loops. It's fine once you're used to it, but the functional patterns from TypeScript/JavaScript aren't there by default. - No optional chaining —
user?.address?.citydoesn't exist. You check each level manually.
Should You Learn Go?
If you write backend services in TypeScript, Go is worth your time. The performance is meaningfully better for CPU-bound work, the standard library is excellent, and the concurrency model is genuinely powerful. The learning curve from TypeScript isn't steep — it's more of a perspective shift than a new paradigm.
I'm using Go for backend work alongside NestJS now, picking the right tool depending on what the project needs.