What is a goroutine, and how is it different from an OS thread?

4 minbeginnergogoroutinesconcurrencyfundamentals

Quick Answer

A goroutine is a lightweight, function-level unit of concurrent execution managed by the Go runtime, started with the go keyword: go doWork(). Unlike an OS thread, a goroutine starts with a tiny stack (about 2KB) that grows and shrinks dynamically, so you can run hundreds of thousands of goroutines cheaply, versus a few thousand OS threads at most. The Go runtime multiplexes many goroutines onto a much smaller number of OS threads (an M:N scheduler), parking goroutines that block on channel operations or I/O without blocking the underlying OS thread. This is what makes goroutines cheap enough to use liberally, for example spawning one per incoming request, where spawning a full OS thread per request would be far too expensive.

Detailed Answer

Goroutines are Go's core concurrency primitive, and their cost model is what makes Go's "just spawn a goroutine" style practical.

Starting one

func sayHello() {
    fmt.Println("hello")
}

go sayHello()          // runs concurrently, doesn't block the caller
time.Sleep(time.Millisecond) // without this, main might exit before sayHello runs

main exiting ends the whole program immediately, even if goroutines are still running. Real code coordinates completion with a sync.WaitGroup or a channel, not time.Sleep.

Why goroutines are cheap

OS threadGoroutine
Initial stack sizeUsually 1-8MB, fixed~2KB, grows/shrinks dynamically
Typical max countA few thousandHundreds of thousands to millions
Scheduled byThe OS kernelThe Go runtime (in user space)
Context switch costRelatively expensive (kernel involved)Much cheaper (userspace scheduler)

The M:N scheduler

The Go runtime maps many goroutines (G) onto a smaller number of OS threads (M), coordinated through logical processors (P), roughly one per CPU core by default. When a goroutine blocks on a channel operation or a network call, the runtime parks it. It lets the OS thread run a different goroutine instead of sitting idle. This is why a Go program with 100,000 goroutines might only use a handful of OS threads under the hood.

Practical implication

Spawning a goroutine per incoming HTTP request, or per item in a batch job, is a completely normal Go pattern precisely because goroutines are this cheap. The same pattern with OS threads would exhaust system resources long before you got to that kind of concurrency.

Related Resources