# How to Replace Manual Search Loops with slices.Contains in Go

> Replace manual search loops with Go 1.21 slices Contains. Simplify slice value checks with this readable, single function call. Learn how to update your Go code.

- Repository: [JetBrains/go-modern-guidelines](https://github.com/jetbrains/go-modern-guidelines)
- Tags: how-to-guide
- Published: 2026-08-30

---

**You can replace verbose for-loops with the generic `slices.Contains` function (available in the standard library since Go 1.21) to check if a slice contains a specific value with a single, readable function call.**

Manual search loops are one of the most common boilerplate patterns in Go codebases. In the JetBrains/go-modern-guidelines repository, you can find several examples where developers iterate over slices to check for membership or locate specific elements. Learning to replace manual search loops with `slices.Contains` eliminates redundant code and makes your intent explicit.

## The Problem with Manual Search Loops

Before Go 1.21, checking whether a slice contains a specific value required writing an explicit `for` loop with a temporary boolean flag:

```go
found := false
for _, v := range items {
    if v == target {
        found = true
        break
    }
}

```

This pattern is verbose, error-prone, and obscures the actual intent of the code. You must initialize a variable, handle the `break` statement correctly, and maintain the loop logic. When searching for elements in [`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go) or validating uniqueness in [`internal/guidelines/schema.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/schema.go), this boilerplate repeats across the codebase, increasing maintenance burden.

## Using slices.Contains from the Go Standard Library

Since Go 1.21, the `slices` package in the standard library provides a generic `Contains` function that collapses the search pattern into a single call. For projects on earlier Go versions, the same function is available in `golang.org/x/exp/slices`.

### Checking for Membership

Replace the manual loop with a direct call to `slices.Contains`:

```go
import "slices" // Go 1.21+ (use "golang.org/x/exp/slices" for older releases)

// Example: check if a slice of strings contains a specific name.
func hasName(names []string, target string) bool {
    return slices.Contains(names, target)
}

```

This approach eliminates the temporary `found` variable and the `break` statement. In [`internal/goversion/goversion.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/goversion/goversion.go), where the code searches for a Go version string in a list, switching to `slices.Contains` would make the presence check immediate and self-documenting.

### Locating the Index

If you need the position of the element rather than just a boolean, use `slices.Index`:

```go
import "slices"

func indexOf(names []string, target string) int {
    return slices.Index(names, target) // returns -1 if not found
}

```

This is useful in [`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go) when iterating over slices to locate a matching guideline ID, as the index can be used to retrieve the full object or determine insertion points.

## Refactoring Real-World Code

The JetBrains/go-modern-guidelines repository contains several files that would benefit from this refactoring:

- **[`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go)**: Lines that iterate over slices to locate a matching guideline ID can replace equality checks with `slices.Contains` for cleaner ID lookup.
- **[`internal/guidelines/schema.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/schema.go)**: Validation loops that verify uniqueness of IDs can use `slices.Contains` to detect duplicates more concisely instead of nested iteration.
- **[`internal/goversion/goversion.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/goversion/goversion.go)**: Loops that search for a Go version string in a list can be simplified using `slices.Contains` for the presence check.

## When to Keep Manual Loops

Despite its convenience, `slices.Contains` is not appropriate for every scenario. Retain manual loops when:

- **You need side effects**: Counting occurrences, logging matches, or performing work on each element requires iterating the full slice.
- **The condition is complex**: `slices.Contains` only checks equality with the `==` operator. For custom predicates, use a manual loop or `slices.IndexFunc`.
- **Early termination depends on external state**: If you must stop the search based on conditions other than simple equality, keep the explicit loop structure.

## Summary

- Import `"slices"` for Go 1.21+ (or `"golang.org/x/exp/slices"` for earlier versions) to access generic slice utilities.
- Replace boolean flag and `break` patterns with `slices.Contains(slice, target)` for membership tests.
- Use `slices.Index` when you need the position of the element rather than just existence.
- Keep manual loops for complex search logic, side effects, or when working with custom comparison functions.

## Frequently Asked Questions

### Is slices.Contains available in all Go versions?

`slices.Contains` was added to the standard library in Go 1.21. If you are maintaining code for earlier Go versions, you can import `golang.org/x/exp/slices` to access the same generic function with identical behavior.

### What if I need to find the position instead of just checking existence?

Use `slices.Index(slice, target)`, which returns the index of the first matching element or `-1` if not found. This avoids the manual loop while still providing the location data you need.

### Can I use slices.Contains with custom comparison logic?

No, `slices.Contains` uses the `==` operator for comparison. If you need to search based on a custom predicate, such as case-insensitive string matching or struct field comparison, you must write a manual loop or use `slices.IndexFunc` with a custom function.

### Does slices.Contains perform better than a manual for-loop?

The `slices.Contains` helper is optimized and may be inlined by the compiler, eliminating function call overhead. While the Big-O complexity remains O(n), the implementation avoids the accumulator variables and branching overhead typical of hand-written loops, often resulting in identical or better performance with significantly cleaner source code.