Go Has No Set: Why and How to Live Without a Built-in Set

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

"Where is my Set?" — The Cry of a Go Developer's Soul

\n\n

If you come to Go from Python, JavaScript, or Java, sooner or later you will ask yourself: "Where the hell is my beloved Set?" — and that's normal. On Stack Overflow, the topic "Golang why don't we have a set datastructure" has gathered thousands of views and dozens of heated discussions. Developers are used to a convenient set with unique elements, but Go only offers arrays, slices, and maps. In this article, we will explore the philosophy of Go's creators, learn how to emulate a Set, and even write our own minimalist implementation.

\n\n

Why Didn't Go's Creators Add a Set to the Standard Library?

\n\n

The answer lies in the language's minimalism and pragmatism. The Go team (Rob Pike, Ken Thompson, Robert Griesemer) adhered to the principle: "If something can be done with one language construct, don't create extra entities." In Go, map is not just a dictionary, but a universal container. It already supports key uniqueness, which is the main property of a set.

\n\n

The Philosophy of "Less Magic, More Clarity"

\n\n

Unlike Python, where set is a separate type with its own methods (.add(), .union()), Go decided not to complicate the language. Why write a separate structure if map[T]struct{} does the same thing? This saves space in the standard library and lowers the entry barrier for beginners. The main thing is to learn how to correctly use a map as a set.

\n\n

How to Emulate a Set in Go: Three Working Approaches

\n\n

Let's move on to practice. Here's how you can implement a set using only Go's standard tools.

\n\n

1. The Most Popular: map[type]struct{}

\n\n

This is the standard approach in the Go community. Why struct{}? Because an empty struct takes up no memory (0 bytes) — perfect as a marker for an element's "existence".

\n\n
package main\n\nimport "fmt"\n\nfunc main() {\n    // Create a set of integers\n    set := make(map[int]struct{})\n\n    // Add elements\n    set[1] = struct{}{}\n    set[2] = struct{}{}\n    set[3] = struct{}{}\n    set[1] = struct{}{} // Duplicate is ignored\n\n    // Check for existence\n    if _, exists := set[2]; exists {\n        fmt.Println("2 is in the set")\n    }\n\n    // Deletion\n    delete(set, 3)\n\n    // Iteration\n    for key := range set {\n        fmt.Println(key)\n    }\n}
\n\n

Pros: zero memory allocation for values, simplicity. Cons: no .Add() or .Contains() methods — everything is done via map syntax.

\n\n

2. Custom Type with Methods

\n\n

If you need a clean API, wrap the map in a struct:

\n\n
package main\n\nimport "fmt"\n\n// Set — generic set (Go 1.18+)\ntype Set[T comparable] struct {\n    items map[T]struct{}\n}\n\n// NewSet creates a new Set\nfunc NewSet[T comparable]() *Set[T] {\n    return &Set[T]{items: make(map[T]struct{})}\n}\n\n// Add adds an element\nfunc (s *Set[T]) Add(item T) {\n    s.items[item] = struct{}{}\n}\n\n// Contains checks for presence\nfunc (s *Set[T]) Contains(item T) bool {\n    _, ok := s.items[item]\n    return ok\n}\n\n// Size returns the size\nfunc (s *Set[T]) Size() int {\n    return len(s.items)\n}\n\nfunc main() {\n    intSet := NewSet[int]()\n    intSet.Add(10)\n    intSet.Add(20)\n    fmt.Println(intSet.Contains(10)) // true\n    fmt.Println(intSet.Size())       // 2\n}
\n\n

This already resembles a full-fledged set datastructure in Go. Generics appeared in version 1.18, so the code works on modern projects.

\n\n

3. Using bool Instead of struct{}

\n\n

Some use map[T]bool, but this wastes 1 byte per value. In most cases, the difference is negligible, but if you are writing a high-load service, it's better to stick with struct{}.

\n\

Blogs

Book Recommendations