Introduction: Why do Python programmers miss "in" in Go?
\n\nIf you came to Go from the world of Python, you surely remember that magical line: if "banana" in fruits:. Short, elegant, readable. And then you open Go and ask yourself: "Does Go have 'if x in' construct similar to Python?" — this is exactly how the most popular question on Stack Overflow sounds, having gathered thousands of views.
Spoiler: there is no direct syntactic sugar like in in Go. But that doesn't mean the task is unsolvable or difficult to solve. On the contrary, Go has several idiomatic ways to check for membership, each efficient and beautiful in its own way.
In this article, we will cover:
\n- \n
- Why Go doesn't have
inand how the language philosophy influenced this; \n - How to check for an element in a slice (including using the new
slices.Containsfunction); \n - How searching in a map works (spoiler: it's the fastest way); \n
- Searching for a substring in a string using
strings.Contains; \n - Writing our own universal wrapper function for a "Pythonic" style; \n
- Comparing the performance of the approaches. \n
Why doesn't Go want "if x in"?
\n\nGo is a minimalist language. Its creators (Rob Pike, Ken Thompson, Rob Griesemer) deliberately avoided overloading the syntax. Instead of adding the keyword in, they suggested using explicit loops and built-in constructs.
This is not a flaw, but a feature: you always see exactly what is happening. For example, for a map, the syntax value, ok := myMap[key] gives you not only a membership check but also the value itself — this is more powerful than just a boolean True/False.
Method 1: Searching in a slice (classic)
\n\nThe most common scenario. Let's say you have a slice of strings and need to check if "Go" is in it.
\n\nBefore Go version 1.21, you had to write the loop manually:
\n\npackage main\n\nimport "fmt"\n\nfunc contains(slice []string, target string) bool {\n for _, v := range slice {\n if v == target {\n return true\n }\n }\n return false\n}\n\nfunc main() {\n languages := []string{"Python", "Java", "Go", "Rust"}\n if contains(languages, "Go") {\n fmt.Println("Go found!")\n }\n}\n\n\nThis code is idiomatic Go. It is explicit, fast, and understandable. For small slices (up to 1000 elements), this linear search works perfectly.
\n\nMethod 1.1: Using slices.Contains (Go 1.21+)
\n\nStarting with Go version 1.21 (released in August 2023), the standard library introduced the slices package with a ready-made Contains function. Now your code becomes almost as concise as in Python:
package main\n\nimport (\n "fmt"\n "slices"\n)\n\nfunc main() {\n languages := []string{"Python", "Java", "Go", "Rust"}\n if slices.Contains(languages, "Go") {\n fmt.Println("Go found! (via slices.Contains)")\n }\n}\n\n\nImportant: slices.Contains is a generic function. It works with any type that supports the == operator (strings, numbers, booleans, structs without slices/maps inside).
Method 2: Searching in a map — the king of performance
\n\nIn Python, you use in for dictionaries, and it's constant time O(1). In Go, it's the same, but the syntax is different:
package main\n\nimport "fmt"\n\nfunc main() {\n // Create a map as a set\n fruits := map[string]bool{\n "apple": true,\n "banana": true,\n "cherry": true,\n }\n\n if fruits["banana"] {\n fmt.Println("banana exists!")\n }\n\n // A more correct way — with the ok check\n if _, ok := fruits["orange"]; ok