Skip to content
Theory · Strings · lesson 5 of 8

The strings package

The same text tasks come up in almost every program: find a word, change the case, split a phrase into parts. For them, the Go standard library has the strings package.

Let's go back to the tile picture. The strings package is a set of tools for the row of tiles: find a pattern in it, swap tiles, cut the row into pieces and put them back together. None of the tools touches the old row, because strings are immutable. Each one builds a new row and hands it to you. Let's see the main tools at work:

GO
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Go is fun"
    fmt.Println(strings.Contains(s, "is")) // true
    fmt.Println(strings.ToLower(s))        // go is fun
    words := strings.Split(s, " ")
    fmt.Println(len(words), strings.Join(words, "-")) // 3 Go-is-fun
}

Let's go line by line:

  1. Contains finds "is" in the string and returns true.
  2. ToLower returns a lowercase copy of the string.
  3. Split cuts the phrase at the spaces into three words.
  4. Join glues them back with hyphens, and the program prints 3 Go-is-fun.

Searching for a substring

strings.Contains(s, sub) returns true if the substring sub occurs in s. The search is case-sensitive:

  • strings.Contains("Go", "go") returns false;
  • strings.Contains("привет", "иве") returns true.

Changing the case

strings.ToLower and strings.ToUpper convert a string to lower and upper case, Cyrillic included: strings.ToUpper("мир") returns "МИР".

Strings are immutable, so every function of the package returns its result as a new string, and the original stays the same. To compare words regardless of case, compare their lowercase versions: strings.ToLower("Go") == strings.ToLower("GO") is true.

Splitting and joining

Three functions of the package cut a string into parts and put the parts back together. Let's look at each.

strings.Split

strings.Split(s, sep) cuts a string at a separator and returns a slice of strings, []string. It cuts at every separator, so:

  • if spaces come in a row or at the edges, Split returns empty strings between them;
  • if the separator does not occur in the string, Split returns a slice of one element, the whole string.

Choose Split when the separator is exact and every part between separators matters, even an empty one.

strings.Join

strings.Join(words, sep) does the reverse: it glues a slice into one string and puts the separator between the elements. It takes only a slice of strings, []string. The empty separator "" glues the parts tightly.

strings.Fields

strings.Fields(s) cuts at any number of spaces and returns no empty strings. It also treats tabs and line breaks as spaces. Choose Fields when you need the words and the number of spaces between them does not matter. Let's compare it with Split on one string:

GO
s := " go  is "
fmt.Println(len(strings.Split(s, " ")), len(strings.Fields(s))) // 5 2
fmt.Println(strings.Fields(s))                                  // [go is]
fmt.Println(strings.Join([]string{"go", "is"}, ""))             // gois

Let's walk through the example line by line:

  1. The first Println prints 5 2: Split returned five parts, and Fields two words.
  2. The second Println prints [go is]: Fields kept only the words.
  3. The third Println prints gois: the empty separator glued the parts tightly.

The five parts from Split are:

  • an empty string before the first space;
  • "go";
  • an empty string between the spaces;
  • "is";
  • an empty string at the end.

So strings.Join(strings.Fields(s), " ") removes extra spaces from a phrase: Fields finds the words, and Join puts exactly one space between them.

Building a string in a loop

Interviewers like to ask why a long string is not built with += in a loop. Let's compare the two ways.

The += operator

A string is immutable, so every s += w creates a new string and copies all the bytes gathered so far into it. For a thousand words that is a thousand copies, each longer than the last. For a few parts outside a loop, the + operator is fine: there are few copies.

strings.Builder

The strings.Builder type gathers the bytes in one place, the way append gathers the elements of a slice:

GO
words := []string{"go", "is", "fun"}
var b strings.Builder
for _, w := range words {
    b.WriteString(w)
}
fmt.Println(b.String()) // goisfun

Let's walk through the example line by line:

  1. var b strings.Builder sets up an empty builder: its zero value is ready for use.
  2. b.WriteString(w) in the loop appends a word to the end without copying everything gathered before.
  3. b.String() returns the finished string, and the program prints goisfun.

Common mistake

A package function is called in the hope that the string itself changes:

GO
s := "go"
strings.ToUpper(s) // ✗
fmt.Println(s)     // go

The program builds and prints go: ToUpper returned a new string, and nothing kept it. Unlike with append, the compiler does not report the unused result here, so the mistake shows only in the output. Assign the result to the variable: s = strings.ToUpper(s).

Key points

  • The functions of the strings package never change a string; they return a new one, and you must keep the result.
  • Split cuts at every separator and keeps empty strings, while Fields cuts at spaces with no empty strings.
  • Join glues a slice of strings with a separator.
  • Build a long string in a loop with strings.Builder: every += copies the whole string again.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way