AbdessamieAbdessamie
Learning Go: My Weekend Deep Dive
Back to Blog

Learning Go: My Weekend Deep Dive

From the official tour to building concurrent backends—my journey into Go (Golang) and why I'm using it for my next SaaS IIoT project.

January 13, 202612 min read
Go
Golang
IoT
Backend
Concurrency

Last weekend, I decided to step out of my comfort zone and dive into Go (Golang). As I prepare to build the backend for my new SaaS IIoT (Industrial Internet of Things) project, I needed a language that could handle high concurrency and provide robust performance without the overhead of heavier runtimes. Go seemed like the perfect candidate.

Coding on a laptop

The Learning Path: The Official Go Tour

I started where most Gophers begin: the Official Go Tour. It’s a fantastic, interactive way to get hands-on with the syntax immediately. But what really stuck with me was how Go compares to the languages I use daily: TypeScript and Python.

1. The Basics: Go vs. Python & TypeScript

Coming from a TypeScript/Python background, Go's strict typing and C-style syntax felt like a hybrid.

  • Variables & Types: unlike Python's dynamic typing, Go is statically typed like TypeScript. However, the type inference (:=) makes it feel almost as concise as Python.
  • Compilation: Go compiles directly to machine code. There's no VM like the JVM or interpreter like Python. This results in incredibly fast startup times—crucial for my IoT lambda functions.
  • Loops: Go has only one looping construct: for. No while, no do-while. You just use for in different ways. This minimalism is a stark contrast to the syntactic sugar I'm used to in JS/TS (like .map, .filter, .reduce), though Go is slowly adopting some of these functional patterns.

2. Structs, Methods, and Interfaces

This is where Go gets interesting. It’s object-oriented but not in the traditional class-based sense I'm used to in Python or ES6 classes.

  • Structs vs Classes: In Python, I define a class. In Go, I define a struct to hold data. It feels very similar to a TypeScript interface or type definition, but it exists at runtime.
  • Methods: You don't put methods inside the struct. You define functions with a receiver. This decoupling of data and behavior was a paradigm shift.
  • Implicit Interfaces: In TypeScript, I might use implements MyInterface. In Go, there is no implements keyword. If a type has the methods defined in an interface, it implements it. Period. This "duck typing" at compile time is powerful but took a moment to grasp.

3. Generics

I was glad to see Generics (introduced in Go 1.18) covered. In TypeScript, I abuse Generics to create reusable components. Go's implementation is stricter but solves the same problem: writing type-safe, reusable code without casting interface{} (Go's version of any) everywhere.

The Real Challenge: Building a Concurrent Web Crawler

To really test my understanding, I decided to build a simple concurrent web crawler. I wanted to fetch a list of URLs in parallel.

In Python, I would use asyncio or threading (battling the GIL). In Node.js, I'd rely on the event loop and Promise.all. In Go, I use Goroutines.

The Setup

Here is the problem: safely crawling multiple URLs concurrently without visiting the same URL twice. This introduces a classic Race Condition if multiple goroutines try to write to the "visited" map at the same time.

The Solution: sync.Mutex

To handle this, I used sync.Mutex to lock the shared resource (the map) during writes.

go
1package main
2
3import (
4 "fmt"
5 "sync"
6 "time"
7)
8
9type SafeCounter struct {
10 mu sync.Mutex
11 v map[string]bool
12}
13
14// Inc checks if a url has been visited.
15// If not, it marks it as visited and returns true.
16func (c *SafeCounter) Visit(key string) bool {
17 c.mu.Lock()
18 // Lock so only one goroutine at a time can access the map c.v.
19 defer c.mu.Unlock()
20
21 if c.v[key] {
22 return false
23 }
24 c.v[key] = true
25 return true
26}
27
28func Crawl(url string, c *SafeCounter, wg *sync.WaitGroup) {
29 defer wg.Done()
30
31 if !c.Visit(url) {
32 fmt.Printf("Skipping %s, already visited\n", url)
33 return
34 }
35
36 fmt.Printf("Crawling %s\n", url)
37 // Simulate network request
38 time.Sleep(100 * time.Millisecond)
39}
40
41func main() {
42 c := SafeCounter{v: make(map[string]bool)}
43 var wg sync.WaitGroup
44
45 urls := []string{
46 "http://golang.org/",
47 "http://google.com/",
48 "http://golang.org/", // Duplicate
49 }
50
51 for _, url := range urls {
52 wg.Add(1)
53 go Crawl(url, &c, &wg)
54 }
55
56 wg.Wait()
57}

Why this is different

In Node.js/JavaScript, this race condition technically doesn't exist in the same way because user code runs on a single thread. You don't need a mutex for memory access, but you do need to manage asynchronous flow.

In Go, these goroutines are running in parallel on multiple CPU cores. Without c.mu.Lock(), two goroutines could read c.v[key] as false simultaneously, and both would proceed to crawl the URL. The Mutex ensures that the critical section (checking and updating the map) is atomic.

Why Go for my IIoT SaaS?

My upcoming project involves handling real-time data from industrial sensors.

  1. True Concurrency: Unlike Node's event loop, Go can utilize all CPU cores for heavy computation if needed.
  2. Performance: Compiled to machine code, fast startup, low memory footprint.
  3. Simplicity: Easy to read and maintain, which is crucial as the codebase grows.
  4. Deployment: Single binary deployment. No "dependency hell" on the server.

What's Next?

Now that I have the basics down and have built a thread-safe crawler, I'm starting the implementation of the IoT backend. I'll be using standard libraries where possible and exploring the ecosystem for MQTT and WebSocket support.

Stay tuned for more updates as I build this out!

Have a project in mind? Need help automating your workflows or building internal tools? I'd love to hear from you.

Available for new projects