Technology Aug 15, 2026 · 3 min read

Why Does Go Have Goroutines? Understanding Go’s Lightweight Concurrency

When I started exploring Go, one feature immediately stood out: go doSomething() Just adding go before a function call can run it concurrently. That got me thinking: what exactly is a goroutine, and why does Go need it? So, I decided to learn the basics. Here's what I found. 👇...

DE
DEV Community
by Ashitosh Lavhate
Why Does Go Have Goroutines? Understanding Go’s Lightweight Concurrency

When I started exploring Go, one feature immediately stood out:

go doSomething()

Just adding go before a function call can run it concurrently.

That got me thinking: what exactly is a goroutine, and why does Go need it?

So, I decided to learn the basics. Here's what I found. 👇

First, what is concurrency?

Imagine you have multiple tasks:

  • Fetching data from an API
  • Processing a file
  • Handling user requests
  • Writing data to a database

If your program handles them one after another, one task may spend a lot of time waiting while everything else is blocked.

Concurrency allows a program to make progress on multiple tasks during overlapping periods of time.

This is especially useful when programs need to handle lots of work efficiently.

What is a goroutine?

A goroutine is a function that runs concurrently with other goroutines.

Creating one is surprisingly simple:

package main

import "fmt"

func sayHello() {
    fmt.Println("Hello from a goroutine!")
}

func main() {
    go sayHello()

    fmt.Println("Hello from main!")
}

The only difference is this:

go sayHello()

The go keyword tells Go to start sayHello() as a goroutine.

But there's a problem 👀

If you run the previous example, you might not always see:

Hello from a goroutine!

Why?

Because once main() finishes, the program exits.

The goroutine may not get enough time to complete.

A simple way to wait for goroutines is using sync.WaitGroup.

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    wg.Add(2)

    go func() {
        defer wg.Done()
        fmt.Println("Task 1 completed")
    }()

    go func() {
        defer wg.Done()
        fmt.Println("Task 2 completed")
    }()

    wg.Wait()

    fmt.Println("All tasks completed")
}

Here:

  • wg.Add(2) tells the WaitGroup to wait for 2 tasks.
  • go starts each function as a goroutine.
  • defer wg.Done() signals that a task has finished.
  • wg.Wait() prevents main() from exiting until both tasks are complete.

Why not just create multiple threads?

This is where Go becomes particularly interesting.

Goroutines are not simply traditional OS threads.

They are lightweight units of concurrent execution managed by the Go runtime. The runtime schedules goroutines onto OS threads, which means you can work with large numbers of goroutines without manually creating and managing a thread for every task.

That's one of the reasons Go is popular for systems and services that need to handle many concurrent operations.

Concurrency vs Parallelism

One thing I also learned while reading about goroutines:

Concurrency and parallelism are not exactly the same thing.

  • Concurrency: Structuring a program so multiple tasks can make progress independently.
  • Parallelism: Actually executing multiple tasks at the same time using multiple processing resources.

A program can be concurrent without every task literally running at the exact same instant.

The bigger picture

Goroutines become even more powerful when combined with channels, which allow goroutines to communicate and synchronize.

So the Go concurrency model is not just:

"Run everything at the same time."

It's about making concurrent programs easier to structure using tools built directly into the language and its runtime.

What I learned today

My biggest takeaway was this:

Goroutines make concurrency feel simple, but Go's runtime is doing a lot of work behind the scenes to manage them efficiently.

What starts with:

go doSomething()

opens the door to learning about scheduling, synchronization, channels, race conditions, parallelism, and scalable systems.

I'm still learning Go, but this was a really interesting first step into understanding how it handles concurrency. 🚀

What Go concept should I explore next: Channels, Interfaces, defer, or something else?

DE
Source

This article was originally published by DEV Community and written by Ashitosh Lavhate.

Read original article on DEV Community
Back to Discover

Reading List