Goroutines: concurrency built into the language itself
JavaScript's event loop (this curriculum's own lesson on it) handles concurrency with a single thread and a callback queue — genuinely effective for I/O-bound work, but unable to use more than one CPU core for actual computation. Operating Systems Fundamentals covered real OS threads as the traditional alternative, with their own real cost. Go's goroutines are a third option, and understanding why requires both of those lessons as background.
Intermediate
3 min read
The problem goroutines solve: OS threads are real, but expensive
Operating Systems Fundamentals' own lesson on processes and threads established that an OS thread has real overhead — a few megabytes of stack space reserved upfront, and a genuinely expensive context switch (also covered there) every time the OS scheduler swaps which thread is running on a CPU core. Launching thousands of OS threads to handle thousands of concurrent connections is possible, but the memory and scheduling overhead scales badly — this is exactly the real cost JavaScript's single-threaded event loop sidesteps entirely, at the cost of never using more than one core for actual computation.
Goroutines: threads, but managed by Go's own runtime, not the OS
func sayHello() { fmt.Println("Hello from a goroutine")}func main() { go sayHello() // launches sayHello CONCURRENTLY, doesn't wait for it fmt.Println("Hello from main") time.Sleep(time.Second) // (a real program would use a proper sync mechanism — lesson 8)}
The go keyword launches a function as a goroutine — a unit of concurrent execution managed entirely by Go's own runtime scheduler, not the operating system directly. A goroutine starts with a tiny stack (a few kilobytes, growing dynamically as needed) rather than a thread's multi-megabyte upfront allocation, and Go's runtime multiplexes potentially hundreds of thousands of goroutines onto a much smaller number of actual OS threads, switching between them far more cheaply than an OS context switch.
Unlike JavaScript's event loop, goroutines can genuinely run in parallel
JavaScript's single-threaded model means only one piece of JavaScript code ever executes at a literal, physical instant, regardless of how many callbacks are queued. Go's runtime scheduler, by default, distributes goroutines across every available CPU core (GOMAXPROCS, defaulting to the number of logical cores) — goroutines genuinely run in parallel on a multi-core machine, not merely interleaved on one core the way JavaScript's callback queue works. This is a real, structural difference with real consequences: genuine parallel execution means genuine data races become possible in a way JavaScript's single-threaded model structurally prevents (covered directly in lesson 9).
Why launching a goroutine is "fire and forget" by default
go sayHello() // returns IMMEDIATELY — main never waits for sayHello to finish on its own
The go keyword launches a goroutine and returns control to the caller instantly, with no built-in way to know when it finishes or get a return value back from it directly — sayHello() above might finish before or after main moves on to its next line, and nothing about the language itself coordinates that. This is deliberate: goroutines are a minimal primitive, and the actual coordination mechanisms — waiting for goroutines to finish, passing results back and forth safely — are built on top of them using channels, covered next lesson.