Concurrency — Overview
JavaScript is single-threaded — Go is genuinely multi-threaded
Section titled “JavaScript is single-threaded — Go is genuinely multi-threaded”As a TypeScript/Node.js developer you live inside the event loop: all your JS code runs on one thread, and the “concurrency” you see is context-switching between callbacks, Promises, and async/await — not true parallelism.
Go is fundamentally different. Goroutines are scheduled by the Go runtime across real OS threads. Multiple goroutines can genuinely execute at the same time on different CPU cores — no Worker Threads required.
Concurrency ≠ Parallelism
Section titled “Concurrency ≠ Parallelism”Rob Pike (one of Go’s creators) put it precisely:
- Concurrency is dealing with many things at once — a structural property.
- Parallelism is doing many things at once — an execution property.
A chef who chops vegetables while water boils is concurrent (managing multiple things). Two chefs working side by side in the same kitchen are parallel (actually doing multiple things simultaneously).
Node.js excels at concurrency (non-blocking I/O), but cannot be truly parallel without Worker Threads or multiple processes. Go handles both naturally.
Concurrency models compared
Section titled “Concurrency models compared”// JavaScript: single-threaded event loop// "concurrency" through callbacks / Promisesasync function fetchData(url: string) { const res = await fetch(url); // yields to event loop return res.json();}
// setTimeout / setInterval schedule callbacks on the loopsetTimeout(() => console.log("later"), 0);console.log("first"); // always prints before "later"// Go: real concurrent goroutines// Each 'go' spawns a lightweight thread managed by the Go runtimepackage main
import ( "fmt" "sync")
func fetchData(url string, wg *sync.WaitGroup) { defer wg.Done() fmt.Println("Fetching:", url) // runs concurrently}
func main() { var wg sync.WaitGroup urls := []string{"api/users", "api/orders", "api/products"} for _, url := range urls { wg.Add(1) go fetchData(url, &wg) } wg.Wait() // block until all goroutines finish}What you will learn in this module
Section titled “What you will learn in this module”- Goroutines —
go f()launches a goroutine instantly; compare to async functions - Channels — typed pipes between goroutines; compare to Promises/callbacks
- Select — choose from multiple channels; compare to
Promise.race - Sync —
WaitGroup,Mutex,RWMutexfor shared-state safety - Context — cancellation and deadlines; compare to
AbortController - Worker Pools — a pattern for bounded concurrent work
package main
import ( "fmt" "sync")
func task(name string, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Task %s running\n", name)}
func main() { var wg sync.WaitGroup tasks := []string{"A", "B", "C"} for _, t := range tasks { wg.Add(1) go task(t, &wg) } wg.Wait() fmt.Println("All tasks complete")}Loading Go runtime (first run only, ~8 MB)…