What a hash set is
A hash set holds unique items and answers "is this item in the set?" in O(1) time on average. Adding an item that is already present changes nothing. Sets fit tasks such as removing duplicates or remembering which values have been visited.
Go has no built-in set type. The usual substitute is a map whose keys are the set's elements and whose values carry no information. Two spellings are common:
map[string]bool: an element is in the set when its value istrue. Reading an absent key givesfalse, soseen[x]works directly as a membership test.map[string]struct{}: the value type is the empty struct, which occupies no memory. Membership is tested with the comma-ok form.
Which spelling to choose is mostly taste. The bool version reads more naturally in conditions. The struct{} version states clearly that the values mean nothing, and it cannot be misused by storing false. Either way the lookup cost is the same, and the keys must be comparable types such as strings, numbers or structs of those.
Operations
Creating and adding
mySet := map[string]struct{}{}
mySet["x"] = struct{}{}
mySet["x"] = struct{}{}
fmt.Println(len(mySet))
1
The second assignment overwrites the same key, so the set still has one element. In struct{}{}, the first struct{} is the type and the second pair of braces builds a value of it. Building a set from a slice is a loop that adds each element, O(n) overall.
Checking membership
_, ok := mySet["x"]
fmt.Println(ok)
true
With a map[T]bool the same test is just if seen["x"] { ... }. Absent keys read as false, and the read does not add them.
Removing
delete(mySet, "x") removes an element. Deleting an absent element does nothing, so it is safe to call without checking.
Iterating
for val := range mySet visits every element in O(n) time, in no guaranteed order. The order can change between runs. If the output must be predictable, record the elements in a slice as you add them, which is what the exercise does. A set of n elements uses O(n) space.
The lesson's exercise
The exercise builds a set of fruits that keeps its insertion order, using a map[string]bool named seen for membership and a slice named fruits for order.
- Start with data containing a duplicate:
fruitsArray := []string{"apples", "bananas", "apples"}. - Declare the two structures, both empty:
seenandfruits. - Loop over
fruitsArray. For eachfruit,!seen[fruit]is true only the first time the fruit appears. Then mark it inseenandappendit tofruits. The second "apples" fails the test and is skipped. - Add "oranges" with the same check-then-add pattern.
- Test for "apples" with
if seen["apples"]and print "We have apples". - Delete "bananas" from
seenonly. The slice still holds it. - Print what remains. Loop over
fruitsand print each fruit thatseenstill contains. The map decides what is present; the slice supplies the order.
fruitsArray := []string{"apples", "bananas", "apples"}
seen := map[string]bool{}
fruits := []string{}
for _, fruit := range fruitsArray {
if !seen[fruit] {
seen[fruit] = true
fruits = append(fruits, fruit)
}
}
if !seen["oranges"] {
seen["oranges"] = true
fruits = append(fruits, "oranges")
}
if seen["apples"] {
fmt.Println("We have apples")
}
delete(seen, "bananas")
for _, fruit := range fruits {
if seen[fruit] {
fmt.Println(fruit)
}
}
The output is:
We have apples
apples
oranges
Two details matter. append may allocate a new backing array, so its result must be assigned back: fruits = append(fruits, fruit). And the program needs the fmt import.
Common mistakes
- Writing to a nil map.
var seen map[string]boolis nil, andseen["a"] = truepanics. Create it withmap[string]bool{}ormake. - Relying on iteration order. Ranging over a map in tests that compare printed output gives flaky results.
- Setting a
boolvalue tofalseinstead of deleting. The key stays in the map,lenstill counts it, andrangestill visits it. Usedeleteto remove an element. - Forgetting that deleting from one structure does not touch the other. The exercise's slice keeps "bananas" after the delete, which is why the final loop checks
seen.
Key takeaways
- A Go set is a map:
map[T]struct{}ormap[T]bool. - Add, test and delete are
O(1)on average; iteration isO(n)and unordered. - Adding an existing element is harmless; deleting an absent one is harmless.
- Pair the set with a slice when you need unique items in a stable order.