Skip to content

Goroutines

In TypeScript you mark a function async and await its result. The function pauses at every await and yields back to the event loop, but everything still runs on one thread:

async function processUser(id: number) {
const user = await fetchUser(id);
return user;
}
await processUser(1); // you must await or .then() to consume it

In Go you prepend go to any function call. That call is dispatched as a goroutine — a lightweight, independently executing unit managed by the Go runtime. No async keyword on the function, no await at the call site:

go processUser(1) // fires immediately in the background

The critical difference: a JavaScript async function is still single-threaded. A goroutine can genuinely run in parallel on a different OS thread.

TypeScript
// TypeScript: async/await (single-threaded event loop)
async function greet(name: string): Promise<void> {
console.log(`Hello from ${name}`);
}
// sequential
await greet("Alice");
await greet("Bob");
// concurrent (still one thread, interleaved via event loop)
await Promise.all([greet("Alice"), greet("Bob")]);
Go
// Go: goroutines (truly concurrent)
package main
import (
"fmt"
"sync"
)
func greet(name string, wg *sync.WaitGroup) {
defer wg.Done() // signal WaitGroup when done
fmt.Printf("Hello from %s\n", name)
}
func main() {
var wg sync.WaitGroup
names := []string{"Alice", "Bob", "Carol"}
for _, name := range names {
wg.Add(1) // register before launching
go greet(name, &wg) // launch goroutine
}
wg.Wait() // block until all goroutines finish
fmt.Println("All done!")
}

sync.WaitGroup is Go’s primary tool for waiting on a group of goroutines. It works like Promise.all but uses a counter instead of an array:

  1. wg.Add(n) — increment the counter before launching (not inside the goroutine)
  2. wg.Done() — decrement when the goroutine finishes (almost always via defer)
  3. wg.Wait() — block until the counter reaches zero

Rule: always call wg.Add(1) before go f(). If you call it inside the goroutine the scheduler might reach wg.Wait() before the goroutine even starts, and Wait returns immediately with work still pending.

Just like immediately-invoked arrow functions in JS, you can launch an anonymous goroutine inline:

TypeScript
// TypeScript: immediately invoked async
(async () => {
const result = await doSomething();
console.log(result);
})();
Go
// Go: immediately launched anonymous goroutine
go func() {
result := doSomething()
fmt.Println(result)
}() // trailing () invokes the function literal immediately
package main
import (
"fmt"
"sync"
)
func printSquare(n int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("%d² = %d\n", n, n*n)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go printSquare(i, &wg)
}
wg.Wait()
fmt.Println("All squares printed")
}
Which keyword launches a goroutine in Go?
When should wg.Add(1) be called?
What is the approximate starting stack size of a goroutine?
Which WaitGroup method signals that a goroutine has finished?