Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
144: Compiling and Matching Regex in Go
I've seen this mistake in countless pull requests, even from developers who have been using Go for a while. They treat the regexp package like a set of quick utility functions, calling regexp.MatchString every time they need to check a value. It looks clean and concise, but it's a hidden performance landmine.
The "MatchString is a shortcut" Trap
When you call regexp.MatchString("^[a-z]+$", input), you aren't just matching a string. Under the hood, Go has to parse that regular expression, compile it into a finite automaton, and then execute it against your input. If you do this inside a loop—say, validating 10,000 email addresses in a CSV import—you are paying the compilation tax 10,000 times for the exact same pattern.
// DON'T DO THIS in a loop
for _, email := range emails {
// This recompiles the regex every single iteration!
if matched, _ := regexp.MatchString(`^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$`, email); matched {
fmt.Println("Valid email:", email)
}
}
It works, sure. But your profiler will eventually show that a massive chunk of your CPU time is spent in regexp.compile rather than actually processing data. It's a classic case of "convenience over correctness."
Pre-compiling for Production Performance
The right way to handle this is to compile your regex once and reuse the resulting *regexp.Regexp object. In most cases, you'll want to do this at the package level using regexp.MustCompile. I prefer MustCompile over Compile for global variables because if your regex is syntactically wrong, the program should panic immediately on startup rather than failing silently later in the execution flow.
var emailRegex = regexp.MustCompile(`^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$`)
func ValidateEmails(emails []string) {
for _, email := range emails {
// Now we are just executing the pre-compiled state machine
if emailRegex.MatchString(email) {
fmt.Println("Valid email:", email)
}
}
}
By moving the compilation outside the loop, you've turned a heavy operation into a lightweight one. Now, the regex is compiled once when the program starts, and each call to MatchString simply traverses the already-built automaton.
Capturing Data with Submatches
Matching is great for booleans, but usually, we need the data inside the match. This is where FindStringSubmatch comes in. It returns a slice of strings: the first element is the full match, and every subsequent element corresponds to a capturing group (the parts in parentheses).
Let's say we're parsing a custom Order ID like ORD-12345-USA where we need the numeric ID and the country code.
var orderRegex = regexp.MustCompile(`^ORD-(\d+)-([A-Z]{3})$`)
func ParseOrder(id string) {
matches := orderRegex.FindStringSubmatch(id)
if len(matches) < 3 {
fmt.Println("Invalid order format")
return
}
// matches[0] is "ORD-12345-USA"
orderNum := matches[1] // "12345"
country := matches[2] // "USA"
fmt.Printf("Order #%s from %s\n", orderNum, country)
}
One quick tip: always check the length of the resulting slice before accessing indices. If the regex doesn't match, FindStringSubmatch returns nil. Trying to access matches[1] on a nil slice is a fast track to a runtime panic.
📋 Practical Task
Extracting Log Levels and Messages from System Logs
You are building a log analyzer. Your task is to create a program that processes a slice of raw log strings and extracts the log level (e.g., INFO, ERROR, WARN) and the message.
The logs follow this specific format: [LEVEL] Message text here
Requirements:
- Define a package-level regular expression using
regexp.MustCompileto capture the level and the message. - Create a function
ParseLog(log string) (string, string, bool)that returns the level, the message, and a boolean indicating if the match was successful. - Ensure your regex specifically captures the text inside the brackets
[]and everything following the closing bracket and the subsequent space.
package main
import (
"fmt"
"regexp"
)
// TODO: Define your compiled regex here
func ParseLog(log string) (string, string, bool) {
// TODO: Implement the matching logic using FindStringSubmatch
return "", "", false
}
func main() {
logs := []string{
"[INFO] Server started on port 8080",
"[ERROR] Database connection failed",
"Invalid log line",
"[WARN] Disk space reaching 90%",
}
for _, l := range logs {
level, msg, ok := ParseLog(l)
if ok {
fmt.Printf("Level: %s | Msg: %s\n", level, msg)
} else {
fmt.Printf("Skipping malformed log: %s\n", l)
}
}
}
There are no comments for now.