The idiom: a map with empty values
Go has no set keyword and no set in the standard library. A map whose keys are the elements and whose values carry nothing does the job.
struct{} is the empty struct type and struct{}{} is its only value. It occupies zero bytes, so the map stores keys and nothing else.
All the map rules apply: keys must be comparable, iteration order is random, a nil map panics on write, and concurrent writes need a lock. The maps page covers each one.
map[T]struct{} or map[T]bool
The other common spelling is map[T]bool. It reads better because a missing key returns false:
seen := map[string]bool{}
seen["a"] = true
if seen["a"] { ... }
The trade-offs:
map[T]struct{} | map[T]bool | |
|---|---|---|
| Value size | 0 bytes | 1 byte (plus alignment) |
| Membership test | _, ok := s[k] | s[k] |
| Ambiguity | none | s[k] = false is a third state |
The third row is the real reason many codebases prefer the empty struct: with bool, someone eventually writes s[k] = false and len(s) stops being the number of members. For small sets the memory difference does not matter.
Removing duplicates from a slice
The most common use of a set is deduplication. This keeps the first occurrence of each value and preserves order:
Output:
[b a c]
[3 1 2]
[1 2 3]
Union, intersection, difference
Set algebra is a few loops. Iterate the smaller set when testing membership in the other, since each lookup is constant time on average.
sorted exists only to make the output stable. Printing a set by ranging over it gives a different order on each run. maps.Keys and slices.Sorted need Go 1.23.
A named type like type set map[string]struct{} is still a map: you index it, range it and delete from it the same way, and you can hang methods on it.
A small generic Set
With generics (Go 1.18), one type covers every comparable element type. This is enough for most programs:
Wrapping the map in a struct hides the struct{}{} noise and guarantees the map is created by the constructor, which removes the nil map panic. Sorted cannot be a method: a method cannot declare its own type parameters or tighten the type's comparable constraint, and sorting needs cmp.Ordered. The generics page explains constraints.
If you need a full-featured set (thread-safe variants, many operations), third-party packages such as github.com/deckarep/golang-set exist. For most code, the map idiom or a 30-line type like this one is what Go programmers use.
Sets of structs
Any comparable type can be an element, including structs with comparable fields. That makes "have I seen this pair" checks direct:
type edge struct{ from, to string }
visited := map[edge]struct{}{}
visited[edge{"a", "b"}] = struct{}{}
Slices and maps cannot be set elements. To track unique slices, convert each one to a comparable key first, for example an array of fixed size, or a string built with fmt.Sprint.
Common mistakes
- Forgetting to initialize.
var s map[string]struct{}is nil; the first add panics. - Printing a set and expecting stable output. Sort the members first.
- Using
map[T]booland storingfalse. Thenlenno longer counts members. Usedeleteto remove.
Frequently Asked Questions
Does Go have a set type?
No. The standard library has no set. The idiom is a map whose values carry no information: map[string]struct{}. Adding is s[k] = struct{}{}, membership is _, ok := s[k], removal is delete(s, k), and the size is len(s).
Should I use map[T]bool or map[T]struct{} for a set in Go?
map[T]struct{} makes intent explicit and its values take zero bytes. map[T]bool reads more naturally (if seen[x]) because a missing key returns false. Both are correct; the memory difference only matters for very large sets. Pick one and stay consistent.
How do I remove duplicates from a slice in Go?
To keep the first occurrence in order, loop and track seen values in a map[T]struct{}, appending only unseen ones. If order does not matter, sort and drop adjacent duplicates: slices.Sort(s); s = slices.Compact(s).