Skip to content

Beginner · Collections and structs · topic 13 of 46 · 35 min

Strings, bytes and runes

Explanation

A Go string is a read-only sequence of bytes, usually holding UTF-8 text. len(s) counts bytes, not characters, so "₹500" has length 6. A rune is one Unicode character; range over a string gives runes. The strings package has the everyday tools: Split, Join, Contains, TrimSpace, ToUpper and strings.Builder.

Bytes, characters and UTF-8

Computers store text as numbers. UTF-8 is the standard way to turn any character from any language into bytes. English letters take 1 byte each, but ₹ takes 3 bytes and each Hindi letter like न also takes 3. A Go string is just those bytes, and Go source files are always UTF-8.

go
s := "₹500"
fmt.Println(len(s))                    // 6 bytes (₹ is 3 bytes)
fmt.Println(utf8.RuneCountInString(s)) // 4 characters
fmt.Println(s[0])                      // 226, the first BYTE, not "₹"
hi := "नमस्ते"
fmt.Println(len(hi), utf8.RuneCountInString(hi)) // 18 6
Import "unicode/utf8" for RuneCountInString. Indexing a string gives a byte (uint8).

A rune is Go's name for one Unicode code point (an int32). A rune literal uses single quotes: 'A', '₹'. Range over a string decodes the UTF-8 and gives you runes with their starting byte index.

go
for i, r := range "Go₹" {
	fmt.Println(i, string(r))
}
// 0 G
// 1 o
// 2 ₹
string(r) turns a rune back into a one-character string.
go
r := []rune("नमस्ते")
fmt.Println(len(r))          // 6
fmt.Println(string(r[0]))    // न
b := []byte("hi")
fmt.Println(b)               // [104 105]
Convert to []rune when you need to work character by character, and to []byte for raw bytes (files, network).
Careful: Common bug: cutting a string by byte position, like name[:3], can split a multi-byte character and give broken text. For non-English text, convert to []rune first or use functions from the strings package.

Strings cannot be changed

Strings are immutable: once made, their bytes never change. s[0] = 'X' is a compile error. To "change" a string you build a new one. This makes strings safe to share between goroutines.

Joining strings efficiently

go
name := "Asha"
msg := "Hello, " + name + "!"            // fine for a few pieces
msg2 := fmt.Sprintf("%s has %d orders", name, 3)

var b strings.Builder
for i := 1; i <= 3; i++ {
	fmt.Fprintf(&b, "Row %d;", i)
}
fmt.Println(b.String()) // Row 1;Row 2;Row 3;
Using + in a long loop copies the whole string every time. strings.Builder grows one buffer instead.

The strings package

FunctionExampleResult
strings.Containsstrings.Contains("Pune city", "city")true
strings.HasPrefixstrings.HasPrefix("+91 98", "+91")true
strings.Splitstrings.Split("a,b,c", ",")[a b c]
strings.Joinstrings.Join([]string{"a", "b"}, "-")a-b
strings.Fieldsstrings.Fields(" chai samosa ")[chai samosa]
strings.TrimSpacestrings.TrimSpace(" hi \n")hi
strings.ToUpperstrings.ToUpper("upi")UPI
strings.ReplaceAllstrings.ReplaceAll("a-b-c", "-", "/")a/b/c
strings.Repeatstrings.Repeat("*", 4)****
strings.EqualFoldstrings.EqualFold("Go", "GO")true (case-insensitive)

Raw strings

A string in backticks is a raw string: backslashes are not escapes, and it can span several lines. It is handy for Windows paths, regular expressions, SQL queries and JSON samples.

go
path := `C:\Users\asha\notes.txt`
query := `
SELECT id, name
FROM students
WHERE marks > 80`
fmt.Println(path)
Inside backticks, \U is just a backslash and a U, not an escape.

Comparing strings

== compares the bytes exactly, so "Go" == "go" is false. < and > compare byte by byte (alphabetical order for plain English). Use strings.EqualFold for case-insensitive comparison of user input like email addresses.

Tip: Use strconv to turn strings into numbers and back (module 1), and fmt.Sprintf to build formatted text. Do not use string(n) for numbers.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Strings, bytes and runes · 1/7

Strings, bytes and runes

Text in Go is stored in strings. Let us see what a string really holds.

Prefer a video by a teacher? Find free videos on “golang strings bytes runes utf8 strings package tutorial” on YouTube ↗

In real life: Masking a phone number on a receipt

Delivery and payment apps show only part of a phone number, like 98XXXXXX10, so a receipt photo does not leak it. The code trims spaces, removes a +91 prefix, checks there are 10 digits, and builds the masked text with strings.Repeat.

go
package main

import (
	"fmt"
	"strings"
)

func mask(phone string) string {
	p := strings.TrimSpace(phone)
	p = strings.TrimPrefix(p, "+91")
	p = strings.ReplaceAll(p, " ", "")
	if len(p) != 10 {
		return "invalid"
	}
	return p[:2] + strings.Repeat("X", 6) + p[8:]
}

func main() {
	fmt.Println(mask(" +91 98765 43210 ")) // 98XXXXXX10
	fmt.Println(mask("12345"))             // invalid
}
Byte slicing is safe here because digits are 1 byte each in UTF-8.

Practice (0/4 done)

Task 1 · Question

What does this print? fmt.Println(strings.ToUpper("upi") + "-" + strings.Repeat("9", 3))

Task 2 · Question

What does len("₹10") return?

Task 3 · Question

What does this print? parts := strings.Split("2025-08-15", "-") fmt.Println(len(parts), parts[1])

Task 4 · Practice

Write func initials(fullName string) string that returns the capital initials of a name, for example "asha rani kulkarni" gives "ARK". Ignore extra spaces.

Quick quiz

Question 1

1.What type do you get from s[0] when s is a string?

Question 2

2.s := "hello"; s[0] = 'H'. What happens?

Question 3

3.Which is best for joining 10,000 pieces of text in a loop?

Question 4

4.What does strings.Fields(" a b ") return?

Question 5

5.What is special about a string in backticks?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What type do you get from s[0] when s is a string?

Answer: byte (uint8)

Indexing a string gives a single byte.

s := "hello"; s[0] = 'H'. What happens?

Answer: Compile error: strings are immutable

You cannot assign to a byte of a string. Build a new string instead.

Which is best for joining 10,000 pieces of text in a loop?

Answer: strings.Builder

Builder appends to one growing buffer instead of copying the whole string each time.

What does strings.Fields(" a b ") return?

Answer: [a b]

Fields splits on runs of white space and drops empty parts.

What is special about a string in backticks?

Answer: It is raw: no escape sequences and it can span lines

Raw string literals keep backslashes and newlines exactly as written.

What does len() return for a String?Related

Answer: Number of bytes

len() counts bytes of UTF-8 data.

From Slices and strings

What does 'a,b,,c'.split(',') give?Related

Answer: ['a', 'b', '', 'c']

Two commas in a row make an empty string between them.

From Strings and template literals

What does immutable mean for strings?Related

Answer: Once made, a string's characters cannot change; methods return new strings

Every "change" creates a new string.

From Strings: methods, interpolation, verbatim and raw strings

What marks the end of a C string?Related

Answer: The null character '\0'

C functions read characters until they meet '\0'.

From Text: C strings and std::string

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Collections and structs” before its exam: Maps: key-value look-ups, Structs and methods.
  2. Module 3 exam: Collections and structs · 15 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Pointers, interfaces and errors. 46 topics left in the course before the final exam.

← Previous: Slices in depth: length, capacity and sharing