ants: a fixed-capacity goroutine pool for Go that recycles workers instead of spawning them
🐜🐜🐜 ants is the most powerful and reliable pooling solution for Go.
At a glance
- What is it?
- ants is an MIT-licensed Go library that caps the number of goroutines a program can run at once, reusing workers instead of creating a new one per task. It suits services that fan out to thousands of tasks and need a hard ceiling on concurrency.
- Who is it for?
- Use ants when a Go service fans out to many short tasks and you want a hard ceiling on live goroutines without hand-rolling a worker channel. Skip it when you need ordered execution, since the README states tasks are not guaranteed to be addressed in order, or when your task count is small enough that plain go statements are simpler to reason about.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The concurrency ceiling Go does not give you by default
A plain `go` statement has no budget. Each call asks the runtime for another goroutine, and the runtime obliges. For a service that fans out per request, per row or per message, that means concurrency is set by load rather than by design: a traffic spike becomes a memory spike, and downstream dependencies see as many parallel calls as the scheduler happens to allow. Developers usually discover this after a queue drains too fast into a database that cannot take it.
ants addresses that by implementing a goroutine pool with fixed capacity, which the README describes as managing and recycling a massive number of goroutines so you can limit the number in your concurrent programs. The audience is Go engineers writing servers, crawlers, batch processors or anything else that submits work faster than it should be executed. It is a library, not a framework: you keep your own task functions and your own error handling, and ants only decides when a worker runs them.
The README also claims efficient memory usage that may even achieve higher performance than unlimited goroutines in Go. That is a claim from the project, not a measurement here. The mechanism behind it is reuse: a bounded set of workers picks up tasks from a queue, so the allocation cost of a goroutine per task disappears.
How the pool queues, recycles and rejects work
The repository layout shows the design split across files. `pool.go` and `pool_func.go` hold the pool types, `worker.go` and `worker_func.go` the workers, and `worker_loop_queue.go`, `worker_queue.go` and `worker_stack.go` the three internal structures used to hold idle workers. `options.go` carries the functional options, and `multipool.go`, `multipool_func.go` and `multipool_func_generic.go` cover the multi-pool variants.
A task submitted through `Submit` goes onto the pool's queue. An idle worker takes it and runs it. When the worker finishes, it does not exit; it returns to the idle set, which is why the pool can absorb a high submit rate without paying goroutine creation costs each time. The README states that overdue goroutines are purged periodically, so idle workers do not accumulate forever.
The nonblocking mechanism is the part worth understanding before you adopt it. If the queue is full and no worker is free, `Submit` does not wait for capacity; the README lists the nonblocking mechanism as a feature, which means the caller has to decide what to do when a task cannot be accepted. That is a deliberate trade-off: it keeps submit latency predictable, and it puts the overload policy in your code rather than inside the pool.
The README is explicit that ordering is not part of the contract: tasks scatter among concurrent workers and are not guaranteed to be addressed in order. If your pipeline depends on sequence, the pool is the wrong layer for that guarantee.
Installing ants v2 and running a first bounded fan-out
The README gives two install paths. For v2 with modules enabled, which is the current line, the command is:
go get -u github.com/panjf2000/ants/v2The module path in `go.mod` is `github.com/panjf2000/ants/v2` and the module requires Go 1.19 or later, so check your toolchain before pulling it in. The v1 path uses `go get -u github.com/panjf2000/ants` without the version suffix; new code should not start there.
A first real use is a pool with a fixed capacity and tasks submitted to it. The README shows `NewPool` with a capacity argument:
p, _ := ants.NewPool(10000)With the pool in hand, the README shows submission through the pool's `Submit` method. Each call hands one function to the pool:
p.Submit(func() {
// task body
})The README also documents a package-level `ants.Submit(func(){})` form, which uses a default pool rather than one you configured. For a service, prefer the pool you created, because that is the object whose capacity you control.
Capacity is not fixed for the lifetime of the process. The README shows `Tune` resizing a live pool and states the method is thread-safe:
pool.Tune(1000) // Tune its capacity to 1000
pool.Tune(100000) // Tune its capacity to 100000When the work is done, `Release()` shuts the pool down, and `ReleaseTimeout(time.Second * 3)` gives it a bounded window to finish. A released pool can be brought back with `Reboot()`, which the README notes in a comment on the example.
Preallocating the worker queue, and when it is the wrong lever
One option deserves separate attention because it is easy to enable for the wrong reason. `WithPreAlloc(true)` makes ants allocate the whole capacity of the pool up front:
p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))The README is specific about when this helps: a pool with very large capacity where each task runs for a long time, so the allocation cost of growing the queue would otherwise be paid repeatedly under load. The trade is memory held from the moment of construction, whether or not the pool ever fills. A pool of 100000 with preallocation reserves room for 100000 workers immediately. For a pool that peaks at a few hundred goroutines, that is waste, and the option should stay off.
This is the clearest example of the library's posture generally. ants gives you knobs, and it expects you to know your workload before turning them. `Tune` is safe to call concurrently, but calling it in a loop as a substitute for sizing the pool correctly just moves the problem into the scheduler. The README does not document a recommended sizing method, and it does not document what happens to queued tasks when capacity is reduced, so treat shrinking a loaded pool as something to verify in your own environment rather than assume.
Panic handling, and the failure modes the README leaves open
The README lists graceful panic handling as a feature, described as preventing programs from crashing. That matters because a panic inside a goroutine you started with `go` takes down the process unless you recover inside that goroutine. Routing tasks through a pool gives the library a place to recover, so one bad task does not end the program. What the README does not document is how the recovered panic is surfaced: no error channel or hook is described, so you should not assume the pool reports the failure to you. If a task can fail in a way you need to observe, recover inside the task function yourself.
The nonblocking submit path is the second thing to plan for. A full pool means a rejected task, and the README does not describe a retry or backpressure mechanism. The overload policy is yours.
The third limitation is ordering, already noted: concurrent workers mean no sequence guarantee.
Finally, ants is the wrong tool when your concurrency is naturally small or your tasks are long-lived. A pool of ten goroutines handling ten websocket connections is not a pool problem; those goroutines should simply exist. The library earns its place when task count is high, task duration is short, and the ceiling on concurrent work is a real constraint you need to enforce.
How ants differs from pond and gammazero/workerpool
The alternatives people search for alongside ants are pond and gammazero/workerpool, and the difference is mostly in what the pool is willing to do for you.
The conventional Go worker pool, which gammazero/workerpool follows, is built from a task channel and a fixed set of goroutines started at construction. Capacity is a constant. There is no resizing, no preallocation option, no reboot, and typically no panic recovery beyond what you write. It is small and predictable, and if that is all you need, it is less machinery.
ants differs in three concrete ways visible in its API. Capacity is mutable at runtime through `Tune`, so a service can widen its pool during a burst without restarting. The idle worker set is backed by different internal structures (`worker_stack.go`, `worker_queue.go`, `worker_loop_queue.go`), which is an implementation choice the README does not explain in detail but which indicates the pool adapts its bookkeeping to load. And the pool can be released and rebooted, so a pool object is not tied to a single lifetime.
pond sits closer to ants in ambition, offering its own pool abstractions, but ants is the one whose README documents panic recovery and the nonblocking submit path as first-class features. The practical difference for a new project is small; the practical difference for an existing service that needs to change concurrency without a deploy is where ants' `Tune` earns its place.
Maintenance, licence and the cost of upgrading
The repository is not archived, and the last push was on 2026-09-19, so the project is being touched. The release cadence is slower than the commit cadence: v2.12.0 was released on 2026-03-20, v2.11.0 on 2025-01-12, and v2.10.0 on 2024-06-18. That is roughly one minor release per year in the recent record, which matters if you depend on new features landing quickly.
The code is MIT licensed, which permits commercial and closed-source use with the licence and copyright notice retained. This is not legal advice; check the LICENSE file and your organisation's policy.
Upgrade cost is low by design. The module path is versioned (`/v2`), the API surface shown in the README is small (construct, submit, tune, release, reboot), and the dependency list in `go.mod` is short: `github.com/stretchr/testify` and `golang.org/x/sync` as direct requirements, with test-only indirect dependencies. The Go version floor is 1.19. The main upgrade risk is behavioural rather than mechanical: if you relied on a particular idle-worker structure or on queueing behaviour under load, a minor release can change it without changing the API, so pin the version and read the release notes before moving.
Editorial conclusion
Use ants when a Go service fans out to many short tasks and you want a hard ceiling on live goroutines without hand-rolling a worker channel. Skip it when you need ordered execution, since the README states tasks are not guaranteed to be addressed in order, or when your task count is small enough that plain go statements are simpler to reason about. Before adopting, read the pkg.go.dev examples for NewPool and Submit and confirm the Go version in go.mod (1.19) matches your toolchain.
Frequently asked questions
How do I install ants v2 in a Go module?
Run go get -u github.com/panjf2000/ants/v2 from your module. The README notes this path assumes GO111MODULE=on, and go.mod declares Go 1.19 as the minimum.
What happens when the ants pool is full and I submit another task?
The README lists a nonblocking mechanism as a feature, so Submit does not wait for a free worker. The README does not describe a built-in retry or backpressure path, which means the rejection policy is up to your code.
Can I change the capacity of an ants pool after creating it?
Yes. The README shows pool.Tune(1000) and pool.Tune(100000) and states the method is thread-safe, so capacity can be adjusted at runtime without recreating the pool.
Does ants guarantee that tasks run in the order I submit them?
No. The README states that tasks scatter among a series of concurrent workers and are not guaranteed to be addressed in order.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/panjf2000-ants)