Strings in Go — Tasks¶
Task 1: Count Bytes vs Characters¶
Difficulty: Easy
Description: Write a function StringStats(s string) that returns the byte count and rune count of a string as two separate values.
Starter Code:
package main
import (
"fmt"
// import what you need
)
func StringStats(s string) (bytes int, runes int) {
// TODO
}
func main() {
b, r := StringStats("Hello, 世界")
fmt.Printf("bytes=%d, runes=%d\n", b, r) // bytes=13, runes=9
}
Expected Output:
Evaluation Checklist: - [ ] Returns correct byte count using len(s) - [ ] Returns correct rune count using utf8.RuneCountInString or range loop - [ ] Handles empty string (both return 0) - [ ] Handles ASCII-only strings (bytes == runes)
Task 2: Reverse a String (Unicode-safe)¶
Difficulty: Easy-Medium
Description: Implement ReverseString(s string) string that reverses the string correctly, handling multi-byte Unicode characters.
Starter Code:
package main
import "fmt"
func ReverseString(s string) string {
// TODO: must work with "Hello, 世界"
// Expected: "界世 ,olleH"
}
func main() {
fmt.Println(ReverseString("Hello")) // olleH
fmt.Println(ReverseString("Hello, 世界")) // 界世 ,olleH
fmt.Println(ReverseString("")) // (empty)
}
Expected Output:
Evaluation Checklist: - [ ] Uses []rune conversion, not []byte - [ ] Handles empty string without panic - [ ] Handles pure ASCII strings - [ ] Handles multi-byte Unicode characters correctly - [ ] Returns a new string (does not modify input)
Task 3: Word Frequency Counter¶
Difficulty: Medium
Description: Write WordFrequency(text string) map[string]int that returns a map of word -> count. Words are case-insensitive and separated by whitespace.
Starter Code:
package main
import (
"fmt"
// import what you need
)
func WordFrequency(text string) map[string]int {
// TODO
}
func main() {
freq := WordFrequency("Go is great. Go is fast. go GO")
for word, count := range freq {
fmt.Printf("%s: %d\n", word, count)
}
// Expected: go:4, is:2, great.:1, fast.:1
}
Expected Output:
(order may vary)Evaluation Checklist: - [ ] Splits on whitespace using strings.Fields - [ ] Normalizes to lowercase with strings.ToLower - [ ] Counts correctly for repeated words - [ ] Handles empty input (returns empty map, not nil) - [ ] Does not panic on single-word input
Task 4: CSV Row Parser¶
Difficulty: Medium
Description: Write ParseCSVRow(line string) []string that splits a comma-separated line into fields and trims whitespace from each field.
Starter Code:
package main
import (
"fmt"
// import what you need
)
func ParseCSVRow(line string) []string {
// TODO: split by comma, trim each field
}
func main() {
fmt.Println(ParseCSVRow("Alice, 30, New York"))
// Expected: [Alice 30 New York]
fmt.Println(ParseCSVRow("Bob,25,London"))
// Expected: [Bob 25 London]
fmt.Println(ParseCSVRow(""))
// Expected: []
}
Expected Output:
Evaluation Checklist: - [ ] Splits correctly on , - [ ] Trims whitespace from each field - [ ] Returns empty slice for empty input (not nil or panic) - [ ] Handles fields with only whitespace - [ ] Uses strings.Split and strings.TrimSpace
Task 5: Build a Simple Template Engine¶
Difficulty: Medium
Description: Implement FillTemplate(tmpl string, vars map[string]string) string that replaces {{key}} placeholders with values from the map.
Starter Code:
package main
import (
"fmt"
// import what you need
)
func FillTemplate(tmpl string, vars map[string]string) string {
// TODO: replace {{key}} with vars[key]
// If key not found, leave placeholder unchanged
}
func main() {
tmpl := "Hello, {{name}}! You have {{count}} messages."
result := FillTemplate(tmpl, map[string]string{
"name": "Alice",
"count": "5",
})
fmt.Println(result)
// Hello, Alice! You have 5 messages.
}
Expected Output:
Evaluation Checklist: - [ ] Correctly replaces all {{key}} patterns - [ ] Leaves unknown keys unchanged - [ ] Uses strings.Builder or strings.NewReplacer for efficiency - [ ] Handles template with no placeholders - [ ] Handles empty vars map
Task 6: Validate Email Format¶
Difficulty: Medium
Description: Write IsValidEmail(email string) bool that performs basic email validation (contains exactly one @, non-empty local and domain parts).
Starter Code:
package main
import (
"fmt"
// import what you need
)
func IsValidEmail(email string) bool {
// TODO: use strings.Cut for @
// local and domain must be non-empty
// domain must contain at least one dot
}
func main() {
fmt.Println(IsValidEmail("user@example.com")) // true
fmt.Println(IsValidEmail("invalid")) // false
fmt.Println(IsValidEmail("@example.com")) // false
fmt.Println(IsValidEmail("user@")) // false
fmt.Println(IsValidEmail("user@nodot")) // false
}
Expected Output:
Evaluation Checklist: - [ ] Uses strings.Cut(email, "@") - [ ] Checks both local and domain are non-empty - [ ] Verifies domain contains a . - [ ] Returns false for multiple @ signs - [ ] Does not use regexp
Task 7: Efficient String Builder¶
Difficulty: Medium
Description: Implement BuildTable(headers []string, rows [][]string) string that renders a simple text table using strings.Builder.
Starter Code:
package main
import (
"fmt"
"strings"
)
func BuildTable(headers []string, rows [][]string) string {
var sb strings.Builder
// TODO: write header row separated by " | "
// then write a separator line of dashes
// then write each data row
}
func main() {
result := BuildTable(
[]string{"Name", "Age", "City"},
[][]string{
{"Alice", "30", "NYC"},
{"Bob", "25", "LA"},
},
)
fmt.Println(result)
}
Expected Output:
Evaluation Checklist: - [ ] Uses strings.Builder throughout - [ ] Uses strings.Join for column joining - [ ] Separator line has appropriate length - [ ] Handles empty rows - [ ] No trailing newline issues
Task 8: String Rotation Check¶
Difficulty: Medium
Description: Write IsRotation(s, t string) bool that returns true if t is a rotation of s (e.g., "abcd" and "cdab").
Starter Code:
package main
import (
"fmt"
// import what you need
)
func IsRotation(s, t string) bool {
// Hint: s+s contains all rotations of s
// TODO
}
func main() {
fmt.Println(IsRotation("abcde", "cdeab")) // true
fmt.Println(IsRotation("abcde", "abced")) // false
fmt.Println(IsRotation("", "")) // true
fmt.Println(IsRotation("a", "b")) // false
}
Expected Output:
Evaluation Checklist: - [ ] Checks that lengths are equal first - [ ] Uses strings.Contains(s+s, t) efficiently - [ ] Handles empty strings correctly - [ ] Handles single-character strings - [ ] Handles Unicode strings
Task 9: Parse Key-Value Config¶
Difficulty: Medium-Hard
Description: Write ParseConfig(data string) map[string]string that parses a multi-line key=value config format, ignoring blank lines and lines starting with #.
Starter Code:
package main
import (
"fmt"
// import what you need
)
func ParseConfig(data string) map[string]string {
// TODO: split by newline
// skip blank lines and # comments
// split each line on first = only
// trim key and value
}
func main() {
config := `
# Database config
host = localhost
port = 5432
name = mydb
# App config
debug = true
`
result := ParseConfig(config)
fmt.Println(result["host"]) // localhost
fmt.Println(result["port"]) // 5432
fmt.Println(result["debug"]) // true
}
Expected Output:
Evaluation Checklist: - [ ] Uses strings.Split on \n - [ ] Uses strings.TrimSpace on each line - [ ] Skips empty lines and # comment lines - [ ] Uses strings.Cut to split on first = - [ ] Trims key and value - [ ] Handles missing = gracefully
Task 10: Longest Common Prefix¶
Difficulty: Hard
Description: Write LongestCommonPrefix(strs []string) string that finds the longest prefix shared by all strings.
Starter Code:
package main
import "fmt"
func LongestCommonPrefix(strs []string) string {
// TODO: efficient implementation using strings
}
func main() {
fmt.Println(LongestCommonPrefix([]string{"flower", "flow", "flight"})) // fl
fmt.Println(LongestCommonPrefix([]string{"dog", "racecar", "car"})) // ""
fmt.Println(LongestCommonPrefix([]string{"alone"})) // alone
fmt.Println(LongestCommonPrefix(nil)) // ""
}
Expected Output:
Evaluation Checklist: - [ ] Returns empty string for nil or empty slice - [ ] Returns the full string for single-element slice - [ ] Correctly finds common prefix byte-by-byte - [ ] Does not panic on strings of different lengths - [ ] Efficient — stops as soon as prefix is determined
Task 11: HTML Tag Stripper¶
Difficulty: Hard
Description: Write StripHTMLTags(html string) string that removes all <...> tags and returns plain text.
Starter Code:
package main
import (
"fmt"
"strings"
)
func StripHTMLTags(html string) string {
var sb strings.Builder
// TODO: iterate through the string
// skip content between < and >
// preserve all other content
}
func main() {
fmt.Println(StripHTMLTags("<b>Hello</b>, <i>World</i>!"))
// Hello, World!
fmt.Println(StripHTMLTags("<p class=\"x\">Text</p>"))
// Text
}
Expected Output:
Evaluation Checklist: - [ ] Uses strings.Builder for output - [ ] Correctly handles nested-like tags (<p class="x">) - [ ] Preserves text between tags - [ ] Handles unclosed tags gracefully - [ ] Handles empty input