"Where is contains?" — the first pain point for a beginner in Go
\n\nImagine: you're writing in Go, you have a slice of strings ["python", "go", "rust", "javascript"], and you need to check if the language "go" is in it. If you came from Python, Ruby, or JavaScript, you automatically write slice.contains("go") — and get a red compilation error. "What nonsense is this?" you think. "Every self-respecting collection has a contains method!"
Calm down, just calm down. Go really does not have a built-in contains method for slices. And this is not a bug, but a feature. The language developers decided that one universal function would not cover all cases (comparing strings, numbers, structs), and left the implementation to the programmer. It sounds scary, but in reality it's simple and elegant. In this article, we'll break down how to write contains for a slice in Go, look at performance, typical pitfalls, and show ready-made solutions.
\n\nWhy is there no contains for a slice in Go?
\nGo is a minimalist language. It has no generics (before version 1.18) and no method overloading. A slice is not an object with methods, but a data structure: a pointer to an array, length, and capacity. Go developers decided that the contains function should be explicit, so the programmer always understands exactly how the comparison happens. This reduces the number of bugs and makes the code predictable.
\nFurthermore, contains for a slice is always a linear search (O(n)), and if you expect O(1), you are using the wrong data structure (you need a map). Go does not want to mask this complexity.
\n\nThe simplest contains for a slice in Go (manual iteration)
\nLet's write contains for a slice of strings. This is the classic you will see in any production code:
\n\npackage main\n\nimport "fmt"\n\n// Contains checks if an element is in a string slice\nfunc Contains(slice []string, target string) bool {\n for _, item := range slice {\n if item == target {\n return true\n }\n }\n return false\n}\n\nfunc main() {\n languages := []string{"python", "go", "rust", "javascript"}\n fmt.Println(Contains(languages, "go")) // true\n fmt.Println(Contains(languages, "csharp")) // false\n}\n\nIt's simple: we loop through the slice, comparing each element with the target. If we find it — true, otherwise false. Important: the == operator works for strings, numbers, boolean values, and pointers. For structs or slices within slices (nested), this won't work.
Contains for numbers
\nFor a slice of integers, the code is almost identical:
\n\nfunc ContainsInt(slice []int, target int) bool {\n for _, v := range slice {\n if v == target {\n return true\n }\n }\n return false\n}\n\nfunc main() {\n numbers := []int{10, 20, 30, 40, 50}\n fmt.Println(ContainsInt(numbers, 30)) // true\n fmt.Println(ContainsInt(numbers, 99)) // false\n}\n\nInconvenient? Yes. But this is Go — explicitness and simplicity above all.
\n\nUniversal contains for a slice in Go (generics, Go 1.18+)
\nWith the release of Go 1.18, generics appeared in the language. Now you can write one contains function for any type that supports comparison:
\n\npackage main\n\nimport "fmt"\n\n// ContainsGeneric works for any comparable type\nfunc ContainsGeneric[T comparable](slice []T, target T) bool {\n for _, item := range slice {\n if item == target {\n return true\n }\n }\n return false\n}\n\nfunc main() {\n strs := []string{"a", "b", "c"}\n ints := []int{1, 2, 3}\n floats := []float64{1.1, 2.2, 3.3}\n\n fmt.Println(ContainsGeneric(strs, "b")) // true\n fmt.Println(ContainsGeneric(ints, 5)) // false\n fmt.Println(ContainsGeneric(floats, 2.2)) // true\n}\n\nThe comparable constraint means the type supports the == and