TS
GO

TypeScript to Go

10 lessons

Progress0%
1Introduction: Transpiled to Compiled Binary2Type Systems: Structural Interfaces3Functions4Objects to Structs5Generics6Error Handling7Async to Goroutines8Ecosystem9Testing10Go Standard Library
All Mirror Courses
TS
GO
Async to Goroutines
MirrorLesson 7 of 10
Lesson 7

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.

Mirror Card
TS
From TypeScript:

In TypeScript, you're familiar with concurrency.

GO
In Go:

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:

GO
Go 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:

TS
TypeScript (What you know)
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];
}
Mirror Card
TS
From TypeScript:

You may be used to different syntax or behavior.

GO
In Go:

Go goroutines are lightweight threads — not async/await over a single event loop

Mirror Card
TS
From TypeScript:

You may be used to different syntax or behavior.

GO
In Go:

go keyword starts a goroutine; channels communicate between goroutines

Mirror Card
TS
From TypeScript:

You may be used to different syntax or behavior.

GO
In Go:

sync.WaitGroup replaces Promise.all for waiting on multiple goroutines

Mirror Card
TS
From TypeScript:

You may be used to different syntax or behavior.

GO
In Go:

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.

TS
TypeScript
// TypeScript: single-threaded, non-blocking via event loop
fetchUser(1).then(u => process(u));
GO
Go
// Go: truly concurrent — runs in parallel
go func() {
    user, err := fetchUser(1)
    process(user)
}()
Common Pitfall
Goroutines are fire-and-forget by default. If main() exits, all goroutines are killed. Use WaitGroup or channels to synchronize.

2. Channels — Typed Communication Pipes

Channels are typed pipes for goroutine communication. Send with ch <- value, receive with value := <-ch. They synchronize execution naturally.

TS
TypeScript
// TypeScript: callbacks or Promises for async results
const result = await fetchUser(1);
GO
Go
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.

TS
TypeScript
const results = await Promise.all([fetchUser(1), fetchUser(2)]);
GO
Go
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 done

4. select — Non-blocking Channel Operations

select lets a goroutine wait on multiple channels simultaneously — like Promise.race() but for channels.

TS
TypeScript
const result = await Promise.race([fetchA(), fetchB()]);
GO
Go
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
Common Pitfall
Don't assume Go works exactly like TypeScript. While the concepts may be similar, the syntax and behavior can differ significantly.

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
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your TypeScript code in Go to practice these concepts.
PreviousNext