Check if a String Contains a Substring in Go (`strings.Contains`)

Tested on RHEL 10.2 with Go 1.26.5
Package go 1.26.5 (strings standard library)
Applies to Any host with Go installed
Privilege Normal user
Scope Check whether a Go string contains a substring with strings.Contains, case-insensitive patterns, multiple needles, ContainsAny, ContainsRune, ContainsFunc, and when to use Index, HasPrefix, HasSuffix, or regexp. Does not cover full regex or Unicode normalization guides.
Related guides String split in Go
String interpolation in Go
Golang cast to string
Getting started with Go
Golang if else
go
strings.Contains("golang tutorial", "golang")

returns true. A missing substring returns false:

go
strings.Contains("golang tutorial", "Go") // false — case-sensitive

strings.Contains checks for a literal substring and returns a boolean. Import strings from the standard library; no third-party package is required.


Use strings.Contains to check for a substring

strings.Contains(s, substr string) bool reports whether substr appears anywhere in s as a contiguous substring. The haystack comes first, then the needle.

go
package main

import (
	"fmt"
	"strings"
)

func main() {
	text := "request failed: connection reset"
	fmt.Println(strings.Contains(text, "failed")) // true
	fmt.Println(strings.Contains(text, "error"))  // false
}
Output

In application code you usually branch on the result:

go
if strings.Contains(text, "error") {
	// handle failure path
}

Matching is case-sensitive, searches anywhere in the string, and is literal substring matching—not regex. Metacharacters such as . and * are ordinary characters inside Contains.


Case-insensitive substring checks

There is no strings.ContainsIgnoreCase. For straightforward ASCII-style text, normalize both sides the same way:

go
found := strings.Contains(
	strings.ToLower(text),
	strings.ToLower(substr),
)

Lowercasing both strings works for many simple checks, but full Unicode or language-aware case matching can require more specialized handling.

strings.EqualFold compares two complete strings for case-insensitive equality. It does not search for a substring inside a longer string—use Contains with normalization when you need “does this text include this phrase, ignoring case?”


Check for multiple substrings

Any substring

Stop when one needle matches—useful when any error keyword in a log line should trigger alerting:

go
func containsAny(haystack string, needles []string) bool {
	for _, n := range needles {
		if strings.Contains(haystack, n) {
			return true
		}
	}
	return false
}

// containsAny("disk full on /var", []string{"error", "failed", "full"}) → true

All substrings

Return false when any required piece is missing—useful when every token must appear:

go
func containsAll(haystack string, needles []string) bool {
	for _, n := range needles {
		if !strings.Contains(haystack, n) {
			return false
		}
	}
	return true
}

// containsAll("level=error service=api", []string{"level=error", "service="}) → true

ContainsAny, ContainsRune, and ContainsFunc

Function Checks
strings.Contains A literal substring
strings.ContainsAny Any rune from another string
strings.ContainsRune One Unicode code point
strings.ContainsFunc Whether any rune satisfies a predicate

ContainsAny does not mean “does the string contain any of these words?” It asks whether any single character from the second argument appears in the haystack:

go
strings.ContainsAny("cloud", "xyzdo") // true — 'o' is in "cloud"

For a whole word, use Contains or loop explicit substrings.

ContainsRune is the specialized single-rune form—handy when the needle is a rune literal instead of a one-character string:

go
strings.ContainsRune("hello", 'l') // true
strings.Contains("hello", "l")     // also true for ASCII

ContainsFunc, added in Go 1.21, runs a predicate on each rune:

go
import "unicode"

strings.ContainsFunc("abc123", unicode.IsDigit) // true

Use it when you need “contains a digit,” “contains whitespace,” or another rune-level test without building a custom loop. For a fixed substring, Contains remains the clearest choice.


When to use Index, HasPrefix, HasSuffix, or regex

Need Use
Boolean substring check strings.Contains
Position of substring strings.Index
Starts with text strings.HasPrefix
Ends with text strings.HasSuffix
Pattern or regular expression regexp
Any rune matches predicate strings.ContainsFunc

strings.Contains is equivalent to strings.Index(s, substr) >= 0. Use Index when you need the offset for slicing or error messages.

Index returns the byte index of the first match, not a rune position. That matters when multi-byte characters precede the match:

go
strings.Index("日本go", "go") // 6 (byte offset); rune position 2

HasPrefix and HasSuffix test only the start or end boundary:

go
strings.HasPrefix("https://example.com", "https://") // true
strings.HasSuffix("main.go", ".go")                // true

For file extensions, prefer HasSuffix or filepath.Ext instead of Contains, which can match ".go" in the middle of a path. For URL structure, use net/url parsing rather than substring checks alone.


Common mistakes and edge cases

Empty substring. strings.Contains("hello", "") returns true. The empty string is a substring of every string. Do not use an empty needle as a guard unless you intend that behavior.

Case sensitivity. Contains is case-sensitive; see the case-insensitive section above when go and Go should match.

Not validation. strings.Contains(line, "ERROR") is a simple presence check in a log line—not proof of a well-formed email, URL, or file type:

go
line := "2026/08/23 12:00:00 ERROR connection reset by peer"
if strings.Contains(line, "ERROR") {
	// count or escalate — not the same as parsing structured logs
}

strings.Contains(email, "@") only shows that @ appears; it does not validate an email address.

ContainsAny is not word search. ContainsAny(s, "go") asks whether g or o appears anywhere, not whether the word go appears as a substring.

Not regex. Contains treats .* as literal characters. Use regexp for patterns.


Summary

Use strings.Contains(haystack, needle) when you need a fast boolean answer for a literal substring. It is the right default for “is this token present anywhere in this text?”—log filtering, simple guards, and CLI output checks.

Normalize with ToLower for simple case-insensitive checks, loop needles for any-or-all matching, and reach for ContainsAny, ContainsRune, or ContainsFunc only when the question is about individual runes—not whole words. Remember that ContainsAny("cloud", "xyzdo") is true because o appears, not because do is a substring.

When you need a position, boundary, or pattern, switch to Index, HasPrefix, HasSuffix, or regexp instead of stretching Contains beyond its job. For splitting fields after you locate text, see string split in Go.


References


Frequently Asked Questions

1. Is strings.Contains case-sensitive?

Yes. golang and Golang are different substrings. For a simple ASCII-heavy case-insensitive check, normalize both sides with ToLower before calling Contains.

2. How do I do a case-insensitive substring check in Go?

There is no ContainsIgnoreCase. Call strings.Contains(strings.ToLower(text), strings.ToLower(substr)). EqualFold compares two complete strings; it does not search for a substring.

3. Why does strings.Contains(s, "") return true?

The empty string is defined as a substring of every string, including an empty haystack. Treat an empty needle as always matching when you design validation.

4. What is the difference between Contains and ContainsAny?

Contains looks for a contiguous substring. ContainsAny reports whether any single rune from the second argument appears anywhere in the haystack—not whether any of several words appear.

5. How do I get the position of a substring?

Use strings.Index. It returns the byte offset of the first match, or -1 when the substring is absent. The value is a byte index, not a rune position.

6. Does strings.Contains support regular expressions?

No. Contains matches a literal substring only. Use the regexp package when you need pattern matching.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)