# deckarep/golang-set v3: a generic set type for Go

> golang-set gives Go the set collection the standard library never shipped, with a threadsafe and a non-threadsafe implementation behind one interface. Version 3.0.0 drops the Mongo driver and BSON support to stay a stdlib-only package.

**deckarep/golang-set** — A simple, battle-tested and generic set type for the Go language. Trusted by GoogleCloudPlatform, Docker, 1Password, Ethereum and Hashicorp.

- Repository: https://github.com/deckarep/golang-set
- Stars: 4,760 · Forks: 294
- Language: Go
- License: NOASSERTION
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/deckarep-golang-set

## The gap golang-set fills for Go developers

Go's standard library has maps and slices and no set. The usual workaround is a map keyed by the element with an empty struct value, which works until you need union, intersection, difference or a membership check that reads clearly at the call site. golang-set fills that gap. The README opens with the claim that it is "the missing generic set collection for the Go language" and that until Go has sets built-in, you should use this.

The audience is Go programmers who already know why they want a set. The README is blunt about the other camp: readers who think the language creators would have added a set if it were needed are told to ignore the repository. That is a fair filter. This is not a package that argues for its own existence; it assumes you arrived with a use case, most likely deduplication, membership testing across a large collection, or set algebra over IDs.

The feature list is modeled after Python's set implementation, which is where the author says the motivation comes from. If you have written Python and missed set when you moved to Go, the API will feel familiar rather than novel.

## One interface, two implementations, and what comparable means

The package exposes a common interface over two backends. One is non-threadsafe and favors performance; the other is threadsafe and favors concurrent use. You pick at construction time by choosing the constructor, and the rest of your code can be written against the shared interface.

Elements must satisfy Go's comparable constraint. The README lists what qualifies: booleans, integers, strings, floats and other primitive types, pointers, arrays, and structs only if all of their fields are comparable independently. That last clause is the one that bites. A struct containing a slice or a map is not comparable, so it cannot be a set element, and the failure appears at compile time when you instantiate the set rather than at runtime.

The README shows the instantiation pattern directly. Its syntax sample is written as `mySet := mapset.NewSet[T]()` where T is some concrete comparable type, followed by the concrete forms for an int set, a string set, a set of structs, and a set that holds anything using the any or empty interface keyword. The README describes that last form as effectively removing type safety, which is worth taking literally. It compiles, but you have given up the reason to use a generic collection in the first place.

The repository layout backs up the two-implementation design: `threadsafe.go` and `threadunsafe.go` sit alongside `set.go` for the interface, with `iterator.go` and `sorted.go` for iteration and ordering support.

## Installing golang-set and building a first set

Installation is a single `go get` against the v3 module path. The README gives this exact command:

```bash
go get github.com/deckarep/golang-set/v3
```

After that, import the package and construct a set for a concrete comparable type. The README's usage section shows the constructor forms, and its comprehensive example imports the package with the alias `mapset`:

```go
import (
  "fmt"
  mapset "github.com/deckarep/golang-set/v3"
)
```

The README then shows how a set is instantiated for a given element type:

```go
mySet := mapset.NewSet[int]()
```

After this runs, you have an empty int set ready for `Add` calls. That is the whole point of the type, and it is the first thing to check in your own code: if you were previously appending to a slice and deduplicating later, the set removes that second pass.

Version 3.0.0 added several methods worth knowing about on day one. The release notes list `Filter` for element filtering, `AppendFrom` for appending from another source, and `IsDisjoint` for testing whether two sets share no elements. If you are coming from v2, `IsDisjoint` and `Filter` are the new names to look for in your editor's autocomplete.

## Version 3.0.0 removes BSON and the Mongo driver

The most consequential change in v3.0.0 is a deletion. BSON marshaling and the Mongo driver support that arrived in v2.9.0 are fully removed. The README explains the reasoning: the community was unhappy about pulling in such a large dependency just for BSON marshaling, and the driver was implicated in a security CVE affecting the project. The stated direction going forward is that golang-set remains a 100 percent stdlib-only package.

That is a real trade-off, not a cleanup. If your service stores sets in MongoDB and relied on the v2.9.0 BSON path, upgrading to v3 will break that code, and the fix is on your side: marshal to a slice or a map before it reaches the driver. The README does not document a migration shim for this, so plan the change as an application-level edit rather than a dependency bump.

The upside is dependency surface. A set library that pulls a database driver into your module graph is a set library that can hand you a CVE you never asked for. Version 3.0.0 also carries performance work: the release notes mention using Go's `maps.Clone()` instead of a custom implementation, removing function call overhead during marshaling, and general method call optimization. Those are the kinds of changes that are hard to feel in a microbenchmark and easy to feel in a hot loop.

## Where golang-set is the wrong choice

The threadsafe implementation is the obvious default when you are unsure, and that is exactly when it is the wrong pick. A mutex on every operation costs something, and the package deliberately ships a non-threadsafe variant for code that owns its data on one goroutine. If your set lives inside a single request handler and never escapes it, the threadsafe constructor is paying for a guarantee you do not need. The README frames the two implementations as a performance-versus-concurrency choice, and that framing is the guidance: choose deliberately rather than defaulting to safe.

There is also a memory question the documentation does not answer. A set of a large struct type stores whole values as map keys, not pointers, so the memory profile differs from a slice of pointers to the same structs. Nothing in the README addresses sizing or capacity planning for sets of large comparable structs, so if you are storing thousands of multi-field structs, measure before you commit.

Finally, the comparable constraint rules out a common case: sets of slices, sets of maps, and structs containing either. There is no workaround inside the package. You would need to key on a hash or a string encoding yourself, at which point you are managing collisions and the set is no longer doing that work for you.

## golang-set versus a plain map[T]struct{}

The honest alternative is not another library. It is the idiom you already write: `map[string]struct{}` for membership, with a helper function for each operation you need. That approach has zero dependencies, works on any Go version, and is understood by every reviewer on your team.

The difference is in what you write and what you test. A map gives you insert and lookup. It does not give you union, intersection, difference, symmetric difference or a disjointness check, and it does not give you a stable iteration story across Go versions. golang-set provides those as methods, and the repository ships `set_test.go`, `threadsafe_test.go`, `threadunsafe_test.go` and `set123_test.go` covering the behavior. The v2.8.0 release added support for true iterators on Go 1.23 and above, with the README pointing to issue #141 for the differences in how iteration behaves between older and newer Go versions. That is a real distinction: map iteration order is unspecified, and set iteration through the package's iterator support is a defined API.

So the decision is not capability, it is repetition. If you write set algebra once in a codebase, use a map. If you write it in ten places and each place has its own subtly different intersection loop, the package earns its import.

## Maintenance, licensing and upgrade cost

The repository is not archived and the last push was on 2026-09-19, four days after v3.0.0 shipped on 2026-09-15. Releases are infrequent but substantive: v2.8.0 in March 2025, v2.9.0 in April 2026, v3.0.0 in September 2026. This is a small package with a long history rather than a fast-moving one, and the release notes read like a maintainer responding to specific community complaints rather than a roadmap.

The module declares Go 1.25 in `go.mod`, and the README states the generic implementation requires Go 1.18 or higher. That gap matters for upgrade planning: the module's own requirement is the binding one, so a project pinned to an older toolchain cannot simply take v3.0.0 without moving its Go version first.

The licence field is reported as NOASSERTION, which means the automated classifier could not match the LICENSE file to a known identifier. The repository does contain a LICENSE file at the top level, so read it directly before you depend on the package in a commercial product. The v2.9.0 BSON support pulled in the Mongo driver, whose licence terms were separate from this project's; v3.0.0 removes that dependency, which simplifies the licence picture rather than complicating it. Nothing here is legal advice, and the file is short enough to read in full.

The upgrade cost from v2 to v3 is concentrated in one place: any code path that marshaled or unmarshaled sets as BSON. Everything else is additive methods and internal performance work.

## Conclusion

Adopt golang-set when your code repeatedly hand-rolls map[T]struct{} for membership tests, intersection or difference, and you want a tested implementation with both a fast single-threaded variant and a mutex-backed one. Do not adopt it if you are still on Go 1.17 or earlier, since the v3 module declares Go 1.25 and the generic syntax needs Go 1.18 or higher, and do not expect BSON marshaling: v3.0.0 removed the Mongo driver and BSON support entirely. Before upgrading, check whether you call BSON methods, and verify that every type you store satisfies Go's comparable rules, because a struct with a slice or map field will not compile as a set element.

## FAQ

### How does golang-set compare to using a plain Go map as a set?

A map gives you insert and lookup but not union, intersection, difference or a disjointness check, which golang-set provides as methods. The package also ships a threadsafe implementation and iterator support, while map iteration order is unspecified. If you only need membership testing in one place, a map is enough.

### How do I install golang-set and create a set of integers?

Run go get github.com/deckarep/golang-set/v3, then construct the set with mapset.NewSet[int]() after importing the package. The README shows the same constructor pattern for strings, structs, and any, which it describes as removing type safety.

### Does golang-set still support BSON marshaling?

No. Version 3.0.0 removed the Mongo driver and BSON support entirely, and the README states the package will remain a 100 percent stdlib-only package going forward. BSON marshaling was introduced in v2.9.0 but the community objected to the large dependency and the driver was affected by a security CVE.

### What Go version does golang-set v3 require?

The module's go.mod declares Go 1.25, and the README states the generic implementation requires Go 1.18 or higher. Support for true iterators arrived in v2.8.0 for Go 1.23 and above.

### What types can I store in a golang-set set?

Any type satisfying Go's comparable constraint: booleans, integers, strings, floats and other primitives, pointers, arrays, and structs only if all of their fields are comparable independently. Slices, maps, and structs containing either cannot be set elements.

## Sources

- [deckarep/golang-set on GitHub](https://github.com/deckarep/golang-set)
- [Issues](https://github.com/deckarep/golang-set/issues)
- [README](https://github.com/deckarep/golang-set/blob/main/README.md)
- [Releases](https://github.com/deckarep/golang-set/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/deckarep-golang-set
