Async to Goroutines
Concurrency
Introduction
In this lesson, you'll learn about async to goroutines in Go. Coming from TypeScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In TypeScript, you're familiar with concurrency.
Go has its own approach to concurrency, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
func fetchUser(id int) (User, error) {
resp, err := http.Get(fmt.Sprintf("/api/users/%d", id))
if err != nil { return User{}, err }
defer resp.Body.Close()
var user User
json.NewDecoder(resp.Body).Decode(&user)
return user, nil
}
func loadUsers() ([]User, error) {
var wg sync.WaitGroup
results := make([]User, 3)
errs := make([]error, 3)
for i := range 3 {
wg.Add(1)
go func(idx int) {
defer wg.Done()
results[idx], errs[idx] = fetchUser(idx + 1)
}(i)
}
wg.Wait()
return results, errors.Join(errs...)
}Comparing to TypeScript
Here's how you might have written similar code in TypeScript:
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
// Concurrent fetches
async function loadUsers() {
const [u1, u2, u3] = await Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3),
]);
return [u1, u2, u3];
}You may be used to different syntax or behavior.
Go goroutines are lightweight threads — not async/await over a single event loop
You may be used to different syntax or behavior.
go keyword starts a goroutine; channels communicate between goroutines
You may be used to different syntax or behavior.
sync.WaitGroup replaces Promise.all for waiting on multiple goroutines
You may be used to different syntax or behavior.
Go concurrency is true parallelism on multi-core — TypeScript is single-threaded
Step-by-Step Breakdown
1. go = Start a Goroutine
The go keyword starts a function in a new goroutine — a lightweight concurrently-running function. Goroutines are multiplexed onto OS threads by the Go runtime.
// TypeScript: single-threaded, non-blocking via event loop
fetchUser(1).then(u => process(u));// Go: truly concurrent — runs in parallel
go func() {
user, err := fetchUser(1)
process(user)
}()2. Channels — Typed Communication Pipes
Channels are typed pipes for goroutine communication. Send with ch <- value, receive with value := <-ch. They synchronize execution naturally.
// TypeScript: callbacks or Promises for async results
const result = await fetchUser(1);ch := make(chan User, 1) // buffered channel
go func() {
user, _ := fetchUser(1)
ch <- user // send
}()
user := <-ch // receive (blocks until value arrives)3. sync.WaitGroup = Promise.all
WaitGroup tracks a group of goroutines. Add(n) sets the count, Done() decrements, Wait() blocks until the count reaches zero.
const results = await Promise.all([fetchUser(1), fetchUser(2)]);var wg sync.WaitGroup
results := make([]User, 2)
for i := range 2 {
wg.Add(1)
go func(idx int) {
defer wg.Done()
results[idx], _ = fetchUser(idx + 1)
}(i)
}
wg.Wait() // blocks until all goroutines done4. select — Non-blocking Channel Operations
select lets a goroutine wait on multiple channels simultaneously — like Promise.race() but for channels.
const result = await Promise.race([fetchA(), fetchB()]);chA := make(chan string, 1)
chB := make(chan string, 1)
go func() { chA <- fetchA() }()
go func() { chB <- fetchB() }()
select {
case result := <-chA:
fmt.Println("A won:", result)
case result := <-chB:
fmt.Println("B won:", result)
}Common Mistakes
When coming from TypeScript, developers often make these mistakes:
- Go goroutines are lightweight threads — not async/await over a single event loop
- go keyword starts a goroutine; channels communicate between goroutines
- sync.WaitGroup replaces Promise.all for waiting on multiple goroutines
Key Takeaways
- Goroutines are truly concurrent lightweight threads — not a single-threaded event loop
- Channels are typed pipes for goroutine communication and synchronization
- sync.WaitGroup tracks goroutine completion — equivalent to Promise.all
- select waits on multiple channels — equivalent to Promise.race