What a Go string is
A Go string is an immutable sequence of bytes. Text is stored as UTF-8, so a character such as h takes one byte, while é takes two. You write string values in double quotes, and := declares the variable and infers its type:
greeting := "Hello"
Two facts shape everything else in this lesson. First, indexing and len work on bytes, not on characters. Second, a string can never be changed in place. Every operation that looks like an edit builds a new string.
Length, indexing and the byte versus character question
len(str) returns the number of bytes. str[i] returns the byte at index i, whose type is byte (an alias for uint8), so it is a number, not text:
str := "Hello"
fmt.Println(str[0], string(str[0]), len(str))
last := str[len(str)-1]
fmt.Println(string(last))
72 H 5
o
Printing the byte directly shows 72, the code of H. Wrapping it in string(...) turns it back into text. The last element is at len(str)-1, because indices start at 0.
For ASCII text, bytes and characters are the same thing. For anything else they differ:
t := "héllo"
fmt.Println(len(t), len([]rune(t)))
6 5
The string holds 6 bytes but 5 characters. A rune is Go's type for one character (a Unicode code point), and converting with []rune(t) gives one element per character.
Concatenation
The + operator joins two strings into a new one. The += operator builds the result piece by piece:
name := "John"
greet := "Welcome"
greet += " to the course, "
greet += name
fmt.Println(greet)
Each += creates a new string from the old one plus the right side and stores it back in the variable. That is cheap for a handful of pieces. In a loop that runs many times, the copying adds up, which is where strings.Builder helps.
Slicing
str[start:end] returns the part from start up to, but not including, end. If you omit end, the slice runs to the end of the string:
lang := "Javascript"
fmt.Println(lang[2:6])
fmt.Println(lang[4:])
vasc
script
Slice indices are byte positions too. Cutting through the middle of a multi-byte character produces broken text, so for non-ASCII strings convert to []rune first and slice that.
Looping over a string
There are two ways to visit every element, and they do different things on non-ASCII text:
myStr := "Andy"
for _, c := range myStr {
fmt.Println(string(c))
}
for i := 0; i < len(myStr); i++ {
fmt.Println(string(myStr[i]))
}
Both print A, n, d, y on separate lines and take O(n) time. The range loop decodes UTF-8 and yields runes, with the index of each rune's first byte. The indexed loop yields single bytes. For "héllo", range gives the indices 0, 1, 3, 4, 5, because é occupies bytes 1 and 2.
Changing a character: convert to runes
Because strings are immutable, str[i] = 'b' does not compile. The way around it is to copy the characters into a []rune, edit the slice, and convert back:
str := "cat"
chars := []rune(str)
chars[0] = 'b'
str = string(chars)
fmt.Println(str)
bat
Single quotes make a rune ('b'), double quotes make a string ("b"). Mixing them up is a compile error.
The exercise, step by step
The exercise turns "Java" into "TypeScript", using each tool above once.
- Create
langholding"Java". - Append
"script"with+=, giving"Javascript". - Take
lang[4:], the part from index 4 on, which is"script", and store it insubstr. - Join
"Type"andsubstrwith+intonewLang, which is"Typescript". - Copy
newLangintocharArraywith[]rune(newLang), so it can be modified. - Set
charArray[4]to'S'. Index 4 is the fifth character, the lowercases. - Convert back with
string(charArray)and assign it tonewLang.
The complete solution:
lang := "Java"
lang += "script"
substr := lang[4:]
newLang := "Type" + substr
charArray := []rune(newLang)
charArray[4] = 'S'
newLang = string(charArray)
After the last line, newLang is "TypeScript". The variable is reused in step 7 with =, not :=, because it already exists.
Common mistakes
- Expecting
str[0]to be a string. It is abyte, so printing it shows a number unless you convert it. - Assuming
lencounts characters. It counts bytes. - Assigning to
str[i]. Strings are immutable; use a[]rune. - Declaring a variable you never use. Go rejects unused variables at compile time.
- Indexing past the end.
str[len(str)]panics at run time.
Key takeaways
- A string is an immutable sequence of UTF-8 bytes;
lenand indexing work on bytes. rangeover a string yields runes;[]rune(str)gives an editable slice of characters.+and+=build new strings;str[a:b]slices by byte positions with the end excluded.- Single quotes are runes, double quotes are strings.