Question
In Go, is there a built-in way to do something like slice.contains(value) for a slice, without manually searching through each element?
For example, if I have a slice and want to know whether a specific value exists in it, do I need to iterate through the slice myself, or is there an alternative approach in Go?
Short Answer
By the end of this page, you will understand how membership checks work for slices in Go, why slices do not provide constant-time contains lookup, and when to use a loop, helper function, or a map instead.
Concept
In Go, a slice is an ordered collection of elements. It is designed for storing and iterating over sequences of values, not for fast membership lookup.
If you want to know whether a slice contains a value, the usual approach is to check each element until you find a match. This is called a linear search.
for _, v := range slice {
if v == target {
// found it
}
}
Why? Because a slice does not maintain any extra structure that tells Go where a value is located. The values are just stored in order. To know whether target exists, Go may need to inspect elements one by one.
Why this matters
Choosing the right data structure affects both clarity and performance:
- Use a slice when order matters or when you mainly iterate over items.
- Use a map when you need fast lookup like “does this value exist?”
For many small slices, a loop is perfectly fine and idiomatic. For repeated membership checks on larger datasets, a map[T]bool or map[T]struct{} is usually a better fit.
Built-in support
Modern Go includes helper functions in the standard library package slices for some common slice operations.
import "slices"
found := slices.Contains(items, target)
However, this still performs a search through the slice internally. It is more convenient, but not asymptotically faster than writing the loop yourself.
Mental Model
Think of a slice like a row of labeled boxes on a shelf.
If someone asks, “Is there a blue box here?”, and the boxes are not indexed by color, you must look at the boxes one by one until you find a blue one or reach the end.
A map is more like a catalog or index. Instead of checking every box, you ask the catalog directly whether a blue box exists.
So:
- Slice = a sequence you scan
- Map = a lookup table you query
That is why contains on a slice requires checking elements, while a map can answer much faster.
Syntax and Examples
Basic linear search with a loop
package main
import "fmt"
func contains(nums []int, target int) bool {
for _, n := range nums {
if n == target {
return true
}
}
return false
}
func main() {
nums := []int{10, 20, 30, 40}
fmt.Println(contains(nums, 30)) // true
fmt.Println(contains(nums, 99)) // false
}
This is the classic and idiomatic approach:
- loop through the slice
- compare each element to the target
- return
trueas soon as a match is found - return
falseif the loop finishes without finding one
Using the standard library slices.Contains
If you are using a recent Go version that includes the package:
Step by Step Execution
Consider this example:
nums := []int{5, 8, 13}
target := 8
found := false
for _, n := range nums {
if n == target {
found = true
break
}
}
Here is what happens step by step:
numsis created with three values:5,8, and13.targetis set to8.foundstarts asfalse.- The loop begins.
- First iteration:
nis5.5 == 8is false.- continue to the next element.
- Second iteration:
nis8.
Real World Use Cases
Checking whether a slice contains a value appears in many practical programs.
Common examples
- User roles: check whether a user has a role like
"admin" - Allowed file extensions: verify whether
".jpg"is in a list of accepted types - Feature flags: see whether a feature name exists in an enabled-features slice
- Input validation: confirm whether a submitted value is one of the permitted options
- Filtering data: keep or skip records based on membership in a known list
Example: validating allowed statuses
func isAllowedStatus(status string) bool {
allowed := []string{"pending", "approved", "rejected"}
for _, s := range allowed {
if s == status {
return true
}
}
return false
}
For a small fixed list, a slice is simple and readable.
Example: frequent API permission checks
If an API checks permissions many times per request, a map is often better:
permissions := []{}{
: {},
: {},
: {},
}
_, ok := permissions[]
Real Codebase Usage
In real Go codebases, developers usually choose between convenience, readability, and lookup performance.
Common patterns
Guard clauses
Membership checks are often used to reject invalid input early.
func createUser(role string) error {
allowed := []string{"admin", "editor", "viewer"}
if !slices.Contains(allowed, role) {
return fmt.Errorf("invalid role: %s", role)
}
return nil
}
This is a guard clause: fail early before continuing.
Validation helpers
Teams often wrap membership logic in reusable helper functions.
func containsString(items []string, target string) bool {
for _, item := range items {
if item == target {
return true
}
}
return false
}
Common Mistakes
1. Expecting slices to support instant membership lookup
A slice does not store an index by value. This means checking membership requires scanning elements.
nums := []int{1, 2, 3}
// There is no built-in property of the slice itself that makes this O(1).
If you need fast repeated lookup, use a map.
2. Thinking slices.Contains is fundamentally faster
This is convenient:
found := slices.Contains(nums, 3)
But it still performs a linear search internally. It improves readability, not the algorithm.
3. Forgetting to stop early
Broken or less efficient version:
func contains(nums []int, target int) bool {
found := false
for _, n := range nums {
if n == target {
found = true
}
}
return found
}
This works, but it keeps scanning even after finding the match.
Better:
Comparisons
| Approach | Best for | Time per lookup | Notes |
|---|---|---|---|
Manual for loop | Simple one-off checks | O(n) | Idiomatic and explicit |
slices.Contains | Readable one-off checks | O(n) | Cleaner syntax, same search cost |
map[T]struct{} lookup | Many repeated checks | Average O(1) | Requires extra memory and setup |
| Sorted slice + binary search | Repeated checks on sorted data | O(log n) | Only works if data is sorted |
Slice vs map
| Feature |
|---|
Cheat Sheet
Quick reference
Check whether a slice contains a value
for _, v := range items {
if v == target {
return true
}
}
return false
Standard library helper
import "slices"
found := slices.Contains(items, target)
Fast repeated lookups with a map
lookup := make(map[string]struct{})
for _, item := range items {
lookup[item] = struct{}{}
}
_, found := lookup[target]
Rules to remember
- Slices do not provide constant-time membership lookup.
- Checking a slice usually means a linear search.
slices.Containsis convenient, but still linear.- Use a map if you need lots of membership checks.
- Use
return trueimmediately when you find a match.
Edge cases
- Empty slice: always returns
FAQ
Is there a built-in contains method on slices in Go?
No. Slices do not have methods like slice.contains(...). In modern Go, you can use slices.Contains, but it is a function from the standard library, not a method on the slice itself.
Do I always need to loop through a slice to check whether it contains a value?
Yes, unless you use another data structure such as a map, or your data is sorted and you use binary search. A regular slice membership check is a linear scan.
Is slices.Contains faster than a for loop in Go?
Not in algorithmic terms. Both are linear-time operations. slices.Contains is mainly a readability and convenience improvement.
When should I use a map instead of a slice in Go?
Use a map when your main question is “does this value exist?” and you need to ask it often. Use a slice when order matters or when you mainly iterate through values.
Can I use == to compare any slice element type?
No. The element type must be comparable. For example, int and string are comparable, but slices themselves are not.
What if my slice is sorted?
If the slice is sorted, you can use binary search for faster lookups than a linear scan. That is useful when you want repeated searches and your data can stay sorted.
Mini Project
Description
Build a small Go program that validates whether user-provided tags are allowed. This demonstrates both ways to perform membership checks: scanning a slice and using a map for faster repeated lookups.
Goal
Create a program that checks whether given tags exist in an allowed list and prints whether each tag is valid.
Requirements
- Create a slice of allowed tags such as
"go","api", and"backend". - Write a helper function that checks whether a slice contains a string.
- Test the helper with several sample tags and print the results.
- Build a map from the same tags and perform the same checks using map lookup.
- Show that both approaches return the same answers.
Keep learning
Related questions
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.
Concatenating Slices in Go with append
Learn how to concatenate two slices in Go using append and the ... operator, with examples, pitfalls, and practical usage.
Convert String to Integer in Go: Idiomatic Parsing with strconv.Atoi
Learn the idiomatic way to convert a string to an int in Go using strconv.Atoi, with examples, errors, and common mistakes.