Playground , Repo

Why do you need concurrency limiting?

Concurrency limiting in your go application might be necessary to limit overuse of a specific resource. This could be an API rate limit, slow network connection, slow disk I/O operation or limited CPU/RAM.

Starting a huge number of go routines that use a limited resource could cause your application to crash. If you are experiencing crashes or errors on a resource intensive task then I would recommend implementing a concurrency limiter.

How do you implement concurrency limiting?

A buffered channel is the most basic implementation. Write to the channel to acquire a position in the limiter. Read from the channel to open a new position in the limiter. Allow the blocking nature of a channel that is full to stop the execution of individual go routines, then resume execution when the channel is unblocked.

Example Limiter

The following example uses a buffered channel to limit the number of concurrent go routines. The Run() method will start a go routine then pause it if the channel is full. The Wait() method will block until all the go routines have finished executing.

package limiter

import "sync"

type Limiter struct {
	pool chan struct{}
	wg   sync.WaitGroup

func New(n int) *Limiter {
	return &Limiter{pool: make(chan struct{}, n), wg: sync.WaitGroup{}}

func (l *Limiter) Run(task func()) {
	go func() {
		l.pool <- struct{}{}

func (l *Limiter) Wait() {

Example Usage

package main

import (


func main() {
	limiter := limiter.New(10)
	for i := 0; i < 1_000; i++ {
		i := i
		limiter.Run(func() {
			// code to be executed with the limiter
			fmt.Println("hello", i)