slices.Contains vs slices.Index vs slices.IndexFunc: Comparing Go Slice Search Operations

The slices.Contains, slices.Index, and slices.IndexFunc functions from golang.org/x/exp/slices provide idiomatic, O(n) slice lookups—returning a boolean, an index by equality, or an index by predicate, respectively—eliminating the need for manual search loops.

Modern Go development recommends replacing hand-written search loops with standard library helpers. According to the JetBrains/go-modern-guidelines repository, these three functions offer distinct approaches to slice membership testing and element location, each optimized for specific search requirements while maintaining linear time complexity.

Function Comparison: Membership, Position, and Predicate Matching

The golang.org/x/exp/slices package provides three complementary functions that iterate over a slice once and stop at the first match. All three run in O(n) time, but they differ in their return types and matching logic.

slices.Contains: Boolean Membership Testing

slices.Contains(slice []E, elem E) bool reports whether elem appears at least once in the slice. This function is ideal when you only need to verify existence without caring about the element's position.

According to the JetBrains/go-modern-guidelines source code, the repository explicitly recommends using this helper as a replacement for manual search loops. The guideline entry "Use slices.Contains instead of a manual search loop" appears in internal/guidelines/guidelines.json at lines 816–818, with additional mention in the README.md at line 7.

slices.Index: Finding Positions by Equality

slices.Index(slice []E, elem E) int returns the index of the first occurrence of elem or -1 if the element is absent. Use this when you need the position to perform subsequent operations like removal, replacement, or accessing adjacent elements.

While the repository does not maintain a separate guideline entry specifically for slices.Index, the same modernization principles apply—prefer this standard helper over custom for … range constructs that track indices manually.

slices.IndexFunc(slice []S, f func(S) bool) int evaluates a user-supplied predicate function against each element, returning the first index where f(element) returns true, or -1 if no match occurs. This enables complex matching conditions beyond simple equality, such as checking struct fields, derived values, or custom business rules.

The JetBrains guidelines specifically endorse this pattern in internal/guidelines/guidelines.json at lines 866–868 with the entry "Use slices.IndexFunc to find an element by predicate," demonstrating its utility for non-trivial search conditions.

Source Code Recommendations and Complexity

All three functions share identical performance characteristics: they traverse the slice sequentially and terminate immediately upon finding a match. This short-circuit behavior ensures optimal performance even for large datasets when matches appear early.

The internal/guidelines/guidelines.json file serves as the authoritative source for these recommendations, while FEATURES.md (lines 1110–1131) provides concrete implementation examples demonstrating slices.Contains in production scenarios.

Practical Implementation Example

The following example demonstrates all three functions working with different data types and search requirements:

package main

import (
	"fmt"
	"golang.org/x/exp/slices"
)

type Item struct {
	ID   int
	Name string
}

func main() {
	items := []int{3, 7, 9, 12}
	people := []Item{
		{ID: 1, Name: "Alice"},
		{ID: 2, Name: "Bob"},
		{ID: 3, Name: "Carol"},
	}

	// slices.Contains - membership test
	hasSeven := slices.Contains(items, 7)
	fmt.Printf("contains 7? %v\n", hasSeven)

	// slices.Index - find position by equality
	idx := slices.Index(items, 9)
	fmt.Printf("index of 9: %d\n", idx)

	// Not found returns -1
	idx = slices.Index(items, 5)
	fmt.Printf("index of 5: %d\n", idx)

	// slices.IndexFunc - find by custom predicate
	idx = slices.IndexFunc(people, func(p Item) bool { return p.Name[0] == 'B' })
	if idx >= 0 {
		fmt.Printf("first B-named person: %+v\n", people[idx])
	}
}

Output:


contains 7? true
index of 9: 2
index of 5: -1
first B-named person: {ID:2 Name:Bob}

Summary

  • slices.Contains returns a bool for simple membership tests, replacing manual loops that only check for existence.
  • slices.Index returns an int representing the first matching index (or -1), useful when you need to know where an element resides.
  • slices.IndexFunc returns an int based on a predicate function, enabling complex matching logic on struct fields or computed values.
  • All functions reside in golang.org/x/exp/slices, operate in O(n) time, and short-circuit at the first match.
  • The JetBrains/go-modern-guidelines repository explicitly recommends these helpers over hand-written loops in internal/guidelines/guidelines.json.

Frequently Asked Questions

When should I use slices.Index instead of slices.Contains?

Use slices.Index when you need the position of an element, not just whether it exists. If you need to remove the element, update it in place, or access neighboring values, slices.Index provides the necessary integer index. Use slices.Contains only for boolean membership tests where the position is irrelevant.

How does slices.IndexFunc differ from a manual for loop with a break?

slices.IndexFunc provides the same O(n) short-circuit behavior as a manual loop but eliminates boilerplate and reduces error risk. The function encapsulates the iteration logic, boundary checking, and early return pattern, making the code more readable and less prone to off-by-one errors compared to explicit for i, v := range slice constructs with conditional breaks.

Are these slice search functions part of the Go standard library?

No, they reside in the golang.org/x/exp/slices experimental package, not the standard library slices package introduced in Go 1.21+. The JetBrains/go-modern-guidelines repository specifically references this expansion package. While similar functions exist in the standard library as of newer Go versions, the guidelines target the x/exp implementation for broader compatibility with older Go versions.

What is the time complexity of searching with these helpers?

All three functions operate in O(n) linear time with O(1) space complexity. They iterate through the slice exactly once, stopping immediately upon finding the first match. In the worst-case scenario (element not present or at the end), they examine every element exactly once.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →