A comprehensive list of 100 intermediate-level interview questions and answers for Go (Golang) developers, covering concurrency, error handling, and more.
Prepare for your intermediate Go interviews with these 100 carefully curated questions covering advanced topics in the Go programming language.
What are goroutines, and how do they differ from threads?h2
Goroutines are lightweight, user-space threads managed by the Go runtime, not the operating system. They enable concurrent execution of functions with minimal overhead, using a small initial stack (a few KB) that grows dynamically. Unlike OS threads, which are heavier (1-2 MB stack) and managed by the kernel, goroutines are multiplexed onto a smaller number of OS threads by Go’s scheduler, reducing resource usage and context-switching costs.
Key differences:
- Weight: Goroutines are lighter, allowing thousands to run efficiently, while threads are resource-intensive.
- Management: Goroutines are managed by Go’s runtime, threads by the OS.
- Concurrency: Goroutines simplify concurrent programming with channels and sync primitives, while threads often rely on locks.
- Creation: Starting a goroutine (e.g.,
go myFunc()
) is faster and cheaper than creating a thread.
This makes goroutines ideal for scalable, high-concurrency applications.
How do you create a goroutine?h2
To create a goroutine in Go, prepend the go
keyword to a function call. This launches the function to run concurrently in a lightweight, user-space thread managed by the Go runtime. For example:
func myFunction() { fmt.Println("Running in a goroutine")}
func main() { go myFunction() // Start goroutine time.Sleep(time.Second) // Allow goroutine to run}
The go
keyword schedules myFunction
to execute independently. Without time.Sleep
or synchronization (e.g., channels or sync.WaitGroup
), the main program might exit before the goroutine runs. Goroutines are cheap, with minimal stack overhead, and the Go scheduler multiplexes them onto OS threads for efficient concurrency. Use them for tasks like background processing or parallel computations, ensuring proper synchronization to avoid data races.
What is a channel in Go?h2
A channel in Go is a typed conduit for communication and synchronization between goroutines. It allows safe data exchange by ensuring only one goroutine reads or writes to the channel at a time, preventing data races. Channels are created using the make
function, specifying the data type, e.g., ch := make(chan int)
.
Channels can be:
- Unbuffered: Block until both sender and receiver are ready (synchronous).
- Buffered: Allow sending up to a specified capacity without blocking (asynchronous).
Example:
ch := make(chan string) // Unbuffered channelgo func() { ch <- "Hello" }() // Sendmsg := <-ch // Receive
Channels are used with the <-
operator to send (ch <- value
) or receive (value := <-ch
). They’re essential for coordinating concurrent tasks, like passing results or signaling completion, without explicit locks.
How do you create a buffered channel?h2
To create a buffered channel in Go, use the make
function with the chan
keyword, specifying the channel’s data type and buffer capacity as the second argument. For example:
ch := make(chan int, 5) // Buffered channel with capacity 5
This creates a channel that can hold up to 5 integers before blocking. Sends to the channel (ch <- value
) won’t block until the buffer is full, and receives (value := <-ch
) won’t block until the buffer is empty. Here’s an example:
ch := make(chan string, 2)ch <- "first" // Non-blockingch <- "second" // Non-blockingfmt.Println(<-ch) // Receive: first
Buffered channels are useful for asynchronous communication, allowing goroutines to send data without immediate synchronization, unlike unbuffered channels, which block until both sender and receiver are ready.
Explain the select statement in Go.h2
The select
statement in Go is used to handle multiple channel operations concurrently, allowing a goroutine to wait on multiple channels and proceed with the first one that becomes ready. It resembles a switch
statement but for channels, enabling non-blocking or prioritized communication.
Syntax:
select {case <-ch1: // Handle receive from ch1case ch2 <- data: // Handle send to ch2default: // Optional // Execute if no channel is ready}
Example:
ch1 := make(chan string)ch2 := make(chan string)select {case msg := <-ch1: fmt.Println("Received from ch1:", msg)case msg := <-ch2: fmt.Println("Received from ch2:", msg)default: fmt.Println("No messages ready")}
If multiple channels are ready, select
chooses one randomly. The default
case runs immediately if no channel is ready, preventing blocking. It’s ideal for multiplexing, timeouts, or coordinating multiple goroutines.
What is the sync package, and what are Mutexes?h2
The sync
package in Go provides primitives for managing concurrency and synchronization between goroutines. It includes tools like Mutex
, RWMutex
, WaitGroup
, Once
, and Cond
to coordinate shared resource access and execution.
A Mutex (mutual exclusion lock) is a synchronization primitive used to protect shared resources from concurrent access, preventing data races. It ensures only one goroutine can access a critical section at a time.
Example:
var mu sync.Mutexvar counter int
func increment() { mu.Lock() // Acquire lock counter++ // Critical section mu.Unlock() // Release lock}
func main() { go increment() go increment() time.Sleep(time.Second) fmt.Println(counter) // Safe access}
Lock()
blocks other goroutines from entering the critical section until Unlock()
is called. Use Mutex
for thread-safe operations on shared variables, but avoid overusing to prevent deadlocks or performance bottlenecks.
How do you use WaitGroups in Go?h2
A sync.WaitGroup
in Go coordinates the completion of multiple goroutines, ensuring the main program waits for them to finish. It tracks a count of active goroutines and blocks until the count reaches zero.
Usage:
- Declare a
sync.WaitGroup
. - Call
Add(n)
to set the number of goroutines to wait for. - Call
Done()
in each goroutine when it completes. - Call
Wait()
to block until all goroutines callDone()
.
Example:
var wg sync.WaitGroup
func worker(id int) { defer wg.Done() // Signal completion fmt.Printf("Worker %d done\n", id)}
func main() { wg.Add(2) // Wait for 2 goroutines go worker(1) go worker(2) wg.Wait() // Block until both finish fmt.Println("All workers done")}
WaitGroup
is ideal for ensuring all concurrent tasks complete before proceeding, avoiding premature program termination.
What is context in Go, and why is it used?h2
The context
package in Go provides a way to manage cancellation, deadlines, and request-scoped values across goroutines. A Context
carries deadlines, cancellation signals, and key-value pairs, enabling coordinated control in concurrent and distributed systems.
Key uses:
- Cancellation: Signal goroutines to stop, e.g., when a request is aborted.
- Deadlines/Timeouts: Set time limits for operations, like HTTP requests or database queries.
- Request-scoped values: Pass data (e.g., user IDs) across API boundaries.
Example:
func operation(ctx context.Context) error { select { case <-time.After(1 * time.Second): // Simulate work return nil case <-ctx.Done(): // Check for cancellation return ctx.Err() }}
func main() { ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() err := operation(ctx) fmt.Println(err) // Prints: context deadline exceeded}
It’s used in APIs, HTTP servers, and database operations to ensure graceful termination, resource cleanup, and consistent request handling.
Explain interfaces in Go.h2
Interfaces in Go define a set of method signatures that a type must implement to satisfy the interface. They enable polymorphism without inheritance, allowing different types to share behavior. An interface is implicitly implemented by any type that provides the specified methods, without explicit declaration.
Syntax:
type MyInterface interface { Method1() string Method2(int) error}
Example:
type MyInterface interface { Greet() string}
type Person struct { Name string}
func (p Person) Greet() string { // Person implicitly implements MyInterface return "Hello, " + p.Name}
func main() { var i MyInterface = Person{Name: "Alice"} fmt.Println(i.Greet()) // Output: Hello, Alice}
Key points:
- Implicit implementation: No need to declare a type implements an interface.
- Empty interface (
interface{}
): Accepts any type, used for generic-like behavior. - Composition: Interfaces can embed other interfaces.
- Type safety: Ensures method compatibility at compile time.
Interfaces are central to Go’s flexibility, used in APIs like io.Reader
and http.Handler
.
How do you implement an interface?h2
In Go, implementing an interface is implicit. A type implements an interface by defining all the methods specified in the interface’s signature, without explicitly declaring the relationship. There’s no implements
keyword—Go checks at compile time if the type’s methods match the interface.
Example:
type Speaker interface { Speak() string}
type Person struct { Name string}
func (p Person) Speak() string { // Person implicitly implements Speaker return "Hi, I'm " + p.Name}
func main() { var s Speaker = Person{Name: "Alice"} // Assign Person to Speaker interface fmt.Println(s.Speak()) // Output: Hi, I'm Alice}
Steps:
- Define an interface with method signatures.
- Create a type (e.g., struct) with methods matching the interface’s signatures.
- Use the type as the interface type in assignments or function arguments.
This implicit approach promotes flexibility and loose coupling, widely used in Go’s standard library (e.g., io.Reader
).
What is type assertion in Go?h2
Type assertion in Go extracts the underlying concrete type from an interface value. It’s used to access the specific type and its fields or methods when an interface holds a value of unknown type. The syntax is value.(Type)
, where value
is an interface and Type
is the expected concrete type.
Example:
var i interface{} = "hello"s := i.(string) // Assert i is a stringfmt.Println(s) // Output: hello
If the assertion is incorrect, it causes a panic. To handle this safely, use the two-value form:
s, ok := i.(string)if ok { fmt.Println("String:", s)} else { fmt.Println("Not a string")}
Type assertions are common when working with empty interfaces (interface{}
) or when narrowing down interface types, like in JSON parsing or generic handlers, ensuring type-safe access to underlying data.
What is type switching?h2
Type switching in Go uses a switch
statement with a type assertion to handle different concrete types stored in an interface value. It allows you to branch based on the underlying type of an interface, accessing type-specific fields or methods.
Syntax:
switch v := i.(type) {case Type1: // Handle Type1case Type2: // Handle Type2default: // Handle unknown types}
Example:
var i interface{} = "hello"switch v := i.(type) {case string: fmt.Println("String:", v)case int: fmt.Println("Integer:", v)default: fmt.Println("Unknown type")}
Output: String: hello
Type switching is useful for handling dynamic types, such as in JSON parsing or when working with interface{}
values. It avoids multiple type assertions, making code cleaner and safer by checking types at runtime without panicking.
Explain embedding in structs.h2
Embedding in Go structs allows one struct to include another struct or interface as an anonymous field, inheriting its fields and methods without explicit declaration. This promotes composition over inheritance, enabling code reuse and polymorphism.
Example:
type Person struct { Name string}
func (p Person) Greet() string { return "Hello, " + p.Name}
type Employee struct { Person // Embedded struct ID int}
func main() { e := Employee{Person: Person{Name: "Alice"}, ID: 123} fmt.Println(e.Name) // Access embedded field directly fmt.Println(e.Greet()) // Call embedded method}
Output: Alice
, Hello, Alice
Key points:
- Embedded fields/methods are accessed directly (e.g.,
e.Name
instead ofe.Person.Name
). - Embedding an interface requires the struct to implement its methods.
- Conflicts in field/method names require explicit resolution.
Embedding is widely used in Go for modular, reusable code, like in io.ReadWriter
.
How does error handling work with custom errors?h2
In Go, custom errors are created by implementing the error
interface, which requires a single Error() string
method. You define a custom error type, typically a struct, to encapsulate additional context or metadata.
Example:
type CustomError struct { Code int Msg string}
func (e *CustomError) Error() string { return fmt.Sprintf("Error %d: %s", e.Code, e.Msg)}
func doSomething() error { return &CustomError{Code: 404, Msg: "Resource not found"}}
func main() { err := doSomething() if err != nil { fmt.Println(err) // Output: Error 404: Resource not found }}
Custom errors allow richer error information. Use type assertion or errors.As
to inspect the custom error:
if ce, ok := err.(*CustomError); ok { fmt.Println(ce.Code) // Access Code}
This approach enhances error handling with specific details while maintaining Go’s simplicity.
What is panic and recover in Go?h2
In Go, a panic is a runtime error that halts normal execution, unwinds the stack, and terminates the program unless handled. It’s used for unrecoverable errors, like out-of-bounds array access. A recover function retrieves a panic’s value, allowing graceful handling.
Example:
func main() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered:", r) } }() panic("Critical error") // Triggers panic fmt.Println("This won't run")}
Output: Recovered: Critical error
Key points:
- Panic: Call
panic(value)
to trigger, passing any value (e.g., string or error). - Recover: Use
recover()
in a deferred function to capture the panic value and resume execution. - Use sparingly for exceptional cases, not routine error handling.
defer
ensuresrecover
runs during stack unwinding, preventing program crashes.
How do you use the testing package in Go?h2
The testing
package in Go is used to write and run unit tests and benchmarks. Tests are written in files ending with _test.go
and executed using go test
.
Example:
package main
import "testing"
func Add(a, b int) int { return a + b }
func TestAdd(t *testing.T) { result := Add(2, 3) if result != 5 { t.Errorf("Add(2, 3) = %d; want 5", result) }}
Key points:
- Test functions: Start with
Test
, take a*testing.T
parameter, and use methods liket.Error
ort.Fatal
to report failures. - Run tests: Use
go test
to execute all tests in a package. - Assertions: Use
t.Errorf
for non-fatal errors,t.Fatalf
to stop the test. - Table-driven tests: Define test cases in a slice for multiple inputs.
The package supports parallel tests (t.Parallel
), setup/teardown (TestMain
), and verbose output (go test -v
).
What is benchmarking in Go?h2
Benchmarking in Go measures the performance of code using the testing
package. Benchmarks are written in files ending with _test.go
and run with go test -bench=.
. They use a *testing.B
parameter and iterate code in a loop controlled by b.N
.
Example:
package main
import "testing"
func Fib(n int) int { if n <= 1 { return n } return Fib(n-1) + Fib(n-2)}
func BenchmarkFib(b *testing.B) { for i := 0; i < b.N; i++ { Fib(10) }}
Key points:
- Benchmark functions: Start with
Benchmark
, take*testing.B
, and run the codeb.N
times. - Execution: Use
go test -bench=.
to run benchmarks, reporting time per operation. - Reset timers: Use
b.ResetTimer()
to exclude setup time. - Profiling: Combine with
-cpuprofile
or-memprofile
for deeper analysis.
Benchmarks help optimize code by identifying performance bottlenecks.
Explain the reflect package.h2
The reflect
package in Go enables runtime inspection and manipulation of types and values. It’s used to examine the type, structure, and content of variables dynamically, especially when working with interfaces or unknown types.
Key features:
- Type inspection:
reflect.TypeOf(v)
returns the type ofv
. - Value inspection:
reflect.ValueOf(v)
provides aValue
for accessing fields, methods, or modifying values. - Dynamic operations: Modify values, call methods, or inspect struct fields/tags.
Example:
type Person struct { Name string}
func main() { p := Person{Name: "Alice"} t := reflect.TypeOf(p) // Get type v := reflect.ValueOf(p) // Get value fmt.Println(t.Name()) // Output: Person fmt.Println(v.FieldByName("Name")) // Output: Alice}
Use cases include serialization (e.g., JSON encoding), debugging, or generic code. However, it’s slower and less safe than static typing, so use sparingly for performance-critical code.
What are closures in Go?h2
Closures in Go are anonymous functions that capture and retain access to variables from their surrounding scope, even after the scope has exited. They combine a function with its lexical environment, allowing the function to access those variables later.
Example:
func outer(name string) func() string { return func() string { // Anonymous function return "Hello, " + name // Captures 'name' }}
func main() { greet := outer("Alice") // Returns a closure fmt.Println(greet()) // Output: Hello, Alice}
Key points:
- Variable capture: The inner function retains access to outer scope variables (e.g.,
name
). - Lifetime: Captured variables persist as long as the closure exists.
- Use cases: Common in callbacks, deferred execution, or maintaining state without globals (e.g., in HTTP handlers or iterators).
Closures provide a powerful way to encapsulate state and behavior, enhancing flexibility in concurrent or functional patterns.
How do you handle concurrency safely?h2
In Go, safe concurrency is achieved using goroutines and synchronization primitives to prevent data races and ensure thread-safe operations. Key techniques include:
-
Channels: Use channels for safe data exchange between goroutines. Unbuffered channels synchronize communication, ensuring one goroutine sends while another receives.
Go ch := make(chan int)go func() { ch <- 42 }()result := <-ch -
Mutexes: Use
sync.Mutex
to protect shared resources. Lock critical sections withLock()
and release withUnlock()
.Go var mu sync.Mutexvar counter intmu.Lock()counter++mu.Unlock() -
WaitGroups: Use
sync.WaitGroup
to wait for goroutines to complete, avoiding premature program exit. -
Avoid shared state: Prefer passing data via channels over shared variables.
-
Race detector: Run
go test -race
to detect data races.
These tools ensure safe, predictable concurrent execution without race conditions.
What is the race detector in Go?h2
The race detector in Go is a tool that identifies data races in concurrent programs. A data race occurs when two or more goroutines access the same variable simultaneously, with at least one performing a write, and no synchronization (e.g., mutexes or channels) is used. The race detector is built into the Go toolchain and enabled with the -race
flag.
Usage:
go run -race main.gogo test -race
Example:
var counter intfunc main() { go func() { counter++ }() go func() { counter++ }() time.Sleep(time.Second)}
Running with -race
detects the race condition and outputs a warning with stack traces, pinpointing unsynchronized accesses.
Key points:
- Detects races in shared variables, not all concurrency issues.
- Adds runtime overhead, so use in development/testing, not production.
- Helps ensure safe concurrency by identifying issues early.
Use it to debug and verify thread-safe code.
Explain the net/http package.h2
The net/http
package in Go provides tools for building HTTP servers and clients. It simplifies handling HTTP requests, responses, routing, and middleware, making it a cornerstone for web development in Go.
Key components:
- HTTP Server:
http.ListenAndServe
starts a server, andhttp.Handler
orhttp.HandlerFunc
defines request handling.Go func handler(w http.ResponseWriter, r *http.Request) {fmt.Fprint(w, "Hello, World!")}func main() {http.HandleFunc("/", handler)http.ListenAndServe(":8080", nil)} - Routing:
http.ServeMux
(default multiplexer) maps URL paths to handlers. - Requests/Responses:
http.Request
contains request details (method, URL, headers);http.ResponseWriter
sends responses. - Client:
http.Client
makes HTTP requests (Get
,Post
, etc.). - Middleware: Chain handlers to add functionality like logging or authentication.
It’s lightweight, performant, and integrates with Go’s concurrency model, ideal for building scalable web services. Use third-party frameworks like gorilla/mux
for advanced routing.
How do you create a simple HTTP server in Go?h2
To create a simple HTTP server in Go using the net/http
package, you define a handler function and start the server with http.ListenAndServe
. Here’s an example:
package main
import ( "fmt" "net/http")
func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, World!")}
func main() { http.HandleFunc("/", handler) // Register handler for root path http.ListenAndServe(":8080", nil) // Start server on port 8080}
Steps:
- Import
net/http
. - Define a handler function with signature
func(http.ResponseWriter, *http.Request)
. - Use
http.HandleFunc
to map a URL path (e.g., ”/”) to the handler. - Call
http.ListenAndServe
with a port (e.g., "<8080>8080>") and a multiplexer (nil
for default).
Access http://localhost:8080
in a browser to see “Hello, World!”. The server runs concurrently, handling requests in goroutines. Use http.ServeMux
for custom routing or add middleware for extended functionality.
What are middleware in Go web development?h2
Middleware in Go web development is a function or handler that processes HTTP requests or responses before or after the main handler. It enables reusable logic for tasks like authentication, logging, or request modification, sitting between the server and the final handler.
Example:
func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() next.ServeHTTP(w, r) // Call next handler log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start)) })}
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello!") }) http.ListenAndServe(":8080", loggingMiddleware(mux))}
Key points:
- Middleware takes a
http.Handler
and returns a newhttp.Handler
. - Chains multiple middleware using composition.
- Common uses: logging, authentication, CORS, or request validation.
- Integrates with
net/http
or frameworks likegorilla/mux
.
This enhances modularity and reusability in web applications.
How do you parse JSON in Go?h2
In Go, JSON parsing is done using the encoding/json
package with json.Unmarshal
. It converts a JSON-encoded byte slice into a Go struct or map. Define a struct with fields matching the JSON keys, using tags to map names.
Example:
package main
import ( "encoding/json" "fmt")
type Person struct { Name string `json:"name"` Age int `json:"age"`}
func main() { jsonData := []byte(`{"name":"Alice","age":25}`) var person Person err := json.Unmarshal(jsonData, &person) if err != nil { fmt.Println("Error:", err) return } fmt.Printf("Name: %s, Age: %d\n", person.Name, person.Age)}
Output: Name: Alice, Age: 25
Key points:
- Use
json.Unmarshal
to parse JSON into a struct ormap[string]interface{}
. - Struct tags (e.g.,
json:"name"
) map JSON keys to fields. - Handle errors from
json.Unmarshal
for invalid JSON. - Use pointers to modify the target struct.
What is the encoding/json package?h2
The encoding/json
package in Go provides functions for encoding and decoding JSON data. It allows you to convert Go data structures (structs, maps, slices) to JSON (marshalling) and JSON data to Go structures (unmarshalling).
Key functions:
json.Marshal(v interface{}) ([]byte, error)
: Converts a Go value to JSON bytes.json.Unmarshal(data []byte, v interface{}) error
: Parses JSON bytes into a Go value.json.NewEncoder(w io.Writer)
andjson.NewDecoder(r io.Reader)
: Stream JSON encoding/decoding.
Example:
type Person struct { Name string `json:"name"` Age int `json:"age"`}
func main() { p := Person{Name: "Alice", Age: 25} data, _ := json.Marshal(p) // Encode to JSON fmt.Println(string(data)) // Output: {"name":"Alice","age":25}}
Use struct tags (e.g., json:"name"
) to customize JSON field names. It’s essential for APIs, configuration files, and data serialization.
Explain database/sql package.h2
The database/sql
package in Go provides a standard interface for interacting with SQL databases (or SQL-like systems) in a database-agnostic way. It abstracts database operations, allowing you to write portable code using drivers for specific databases (e.g., MySQL, PostgreSQL).
Key components:
- DB:
sql.DB
represents a database connection pool, managing connections for queries. - Queries: Methods like
Query
,QueryRow
,Exec
perform SQL operations. - Prepared Statements:
Prepare
creates reusable SQL statements for efficiency and security. - Transactions:
Begin
,Commit
,Rollback
manage transactional operations.
Example:
package main
import ( "database/sql" _ "github.com/go-sql-driver/mysql" "log")
func main() { db, err := sql.Open("mysql", "user:password@/dbname") if err != nil { log.Fatal(err) } defer db.Close() rows, err := db.Query("SELECT name FROM users") if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { var name string rows.Scan(&name) log.Println(name) }}
Key points:
- Import a database driver (e.g.,
go-sql-driver/mysql
). - Use
sql.Open
to initializesql.DB
. - Methods like
Scan
extract query results into variables. - Supports connection pooling, safe concurrency, and error handling.
It’s ideal for building scalable, database-driven applications while maintaining driver flexibility.
How do you connect to a database in Go?h2
To connect to a database in Go, use the database/sql
package with a database driver (e.g., github.com/go-sql-driver/mysql
for MySQL). Call sql.Open
to create a sql.DB
connection pool, passing the driver name and connection string.
Example (MySQL):
package main
import ( "database/sql" _ "github.com/go-sql-driver/mysql" "log")
func main() { db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname") if err != nil { log.Fatal(err) } defer db.Close() // Close when done
// Verify connection err = db.Ping() if err != nil { log.Fatal(err) } log.Println("Connected to database")}
Key points:
- Import the driver anonymously (
_
). - Connection string format depends on the database (e.g.,
user:password@tcp(host:port)/dbname
for MySQL). sql.DB
manages a connection pool, safe for concurrent use.- Use
db.Ping()
to confirm connectivity. - Always defer
db.Close()
to release resources.
What is dependency management in Go, and what is Go Modules?h2
Dependency management in Go handles external packages and libraries a project relies on, ensuring consistent builds and versioning. Go Modules, introduced in Go 1.11, is the standard dependency management system, replacing tools like dep
.
Go Modules:
- Uses a
go.mod
file to define the module’s name, Go version, and dependencies. - Tracks dependency versions and ensures reproducible builds.
- Commands like
go get
add dependencies, updatinggo.mod
and downloading packages. - The
go.sum
file records cryptographic hashes for dependency integrity.
Example:
// Initialize a modulego mod init example.com/myapp
// Add a dependencygo get github.com/some/package
This creates go.mod
:
module example.com/myappgo 1.20require github.com/some/package v1.2.3
Key points:
go build
orgo run
resolves dependencies automatically.- Modules support versioning, vendoring, and reproducible builds.
- Use
go mod tidy
to clean up unused dependencies.
Explain vendoring in Go.h2
Vendoring in Go is the practice of storing a project’s dependencies locally within a vendor/
directory to ensure consistent builds, especially in environments with limited network access. It was common before Go Modules but is still supported for compatibility.
With Go Modules (post-Go 1.11), vendoring is optional. You can create a vendor/
directory using:
go mod vendor
This downloads all dependencies listed in go.mod
into vendor/
. To build or run using vendored dependencies:
go build -mod=vendorgo run -mod=vendor
Key points:
- Purpose: Ensures dependencies are locally available, avoiding external fetches.
- Use case: Useful for offline builds or legacy projects.
- Drawbacks: Increases repository size; less common with reliable module proxies.
- Go Modules: Prefer
go.mod
for dependency management, but vendoring is supported for specific needs.
Vendoring guarantees reproducible builds but is rarely needed with modern Go workflows.
What are build tags in Go?h2
Build tags in Go, also called build constraints, are special comments at the top of a Go file that control whether the file is included in a build based on conditions like operating system, architecture, or custom flags. They help manage platform-specific code or conditional compilation.
Syntax:
// +build tag1,tag2
Example:
// +build linux
package main
import "fmt"
func main() { fmt.Println("Running on Linux")}
Another file:
// +build windows
package main
import "fmt"
func main() { fmt.Println("Running on Windows")}
Run with:
go build -tags linux
Key points:
- Tags are specified with
// +build
, followed by comma-separated tags (e.g.,linux
,windows
,amd64
). - Use
!tag
to exclude (e.g.,// +build !windows
). - Combine with
go build -tags "tag1 tag2"
. - Common use: Platform-specific code, feature flags, or testing.
Build tags enable flexible, maintainable code for different environments or configurations.
How do you cross-compile Go programs?h2
Cross-compiling in Go builds a program for a different operating system or architecture than the host system. Go supports this natively by setting the GOOS
(operating system) and GOARCH
(architecture) environment variables during the build process.
Example (compile for Linux on Windows):
GOOS=linux GOARCH=amd64 go build -o myapp
Steps:
- Set
GOOS
(e.g.,linux
,windows
,darwin
) andGOARCH
(e.g.,amd64
,arm
,arm64
). - Run
go build
with the-o
flag to specify the output binary name. - Ensure dependencies are compatible with the target platform.
Example:
package main
import "fmt"
func main() { fmt.Println("Hello, cross-compile!")}
GOOS=windows GOARCH=amd64 go build -o myapp.exe
Key points:
- Run
go env GOOS GOARCH
to check supported platforms. - No external tools required; Go’s toolchain handles it.
- Useful for deploying to servers, containers, or embedded devices.
This ensures binaries work on the target system without rebuilding.
What is the unsafe package, and when should you use it?h2
The unsafe
package in Go provides low-level operations that bypass type safety and memory management, allowing direct memory manipulation and type conversions. It’s primarily used for performance-critical code or interfacing with C libraries.
Key features:
- Pointer arithmetic:
unsafe.Pointer
enables pointer manipulation, unlike standard Go pointers. - Type conversions: Convert between incompatible types (e.g.,
int
to*float64
). - Access struct fields: Get raw memory offsets using
unsafe.Sizeof
,unsafe.Alignof
, orunsafe.Offsetof
.
Example:
package main
import ( "fmt" "unsafe")
func main() { i := 42 p := unsafe.Pointer(&i) fp := (*float64)(p) // Unsafe type conversion fmt.Println(*fp) // Undefined behavior}
When to use:
- Interfacing with C code via
cgo
. - Optimizing performance in rare cases (e.g., low-level data structures).
- Accessing memory layouts for serialization.
Caution: Avoid unless necessary; it risks memory corruption, undefined behavior, and breaks Go’s safety guarantees. Use standard Go for most tasks.
How do you use atomic operations?h2
Atomic operations in Go, provided by the sync/atomic
package, ensure thread-safe manipulation of variables without mutexes. They’re used for low-level, high-performance concurrency, typically for simple operations like counters or flags.
Example:
package main
import ( "fmt" "sync" "sync/atomic")
func main() { var counter int32 var wg sync.WaitGroup wg.Add(2)
go func() { atomic.AddInt32(&counter, 1) // Atomic increment wg.Done() }() go func() { atomic.AddInt32(&counter, 1) wg.Done() }()
wg.Wait() fmt.Println(atomic.LoadInt32(&counter)) // Output: 2}
Key functions:
atomic.AddInt32/64
: Increments or decrements a value atomically.atomic.LoadInt32/64
: Safely reads a value.atomic.StoreInt32/64
: Safely writes a value.atomic.CompareAndSwapInt32/64
: Conditionally updates a value.
Use atomic operations for simple, lock-free concurrency (e.g., counters, flags) to avoid mutex overhead, but prefer channels or mutexes for complex synchronization.
What is the sync.Once type?h2
The sync.Once
type in Go ensures a function is executed exactly once, even in concurrent environments with multiple goroutines. It’s useful for one-time initialization tasks, like setting up a singleton or initializing resources.
Example:
package main
import ( "fmt" "sync")
var once sync.Oncevar value int
func initialize() { value = 42 fmt.Println("Initialized")}
func main() { var wg sync.WaitGroup wg.Add(2) go func() { once.Do(initialize) wg.Done() }() go func() { once.Do(initialize) wg.Done() }() wg.Wait() fmt.Println("Value:", value)}
Output: Initialized
, Value: 42
Key points:
once.Do(f)
runsf
only once; subsequent calls are ignored.- Thread-safe, preventing race conditions during initialization.
- Ideal for lazy initialization or setup tasks like database connections.
- Non-reentrant; avoid recursive calls to
once.Do
.
Use sync.Once
for efficient, safe, one-time execution in concurrent programs.
Explain sync.Map.h2
The sync.Map
type in Go, part of the sync
package, is a thread-safe map designed for concurrent access without explicit locking. Unlike a regular map
with a sync.Mutex
, sync.Map
optimizes for high-read, low-write scenarios, using atomic operations and internal separation of read and write paths.
Key methods:
Store(key, value)
: Sets a key-value pair.Load(key)
: Retrieves a value, returning the value and a boolean (ok
).Delete(key)
: Removes a key.Range(f func(key, value interface{}) bool)
: Iterates over entries.
Example:
var m sync.Map
func main() { m.Store("key1", 42) if v, ok := m.Load("key1"); ok { fmt.Println(v) // Output: 42 } m.Delete("key1")}
Use sync.Map
for high-concurrency scenarios with frequent reads and infrequent writes, like caching. Avoid for single-goroutine or lock-based needs, where a regular map with sync.Mutex
is simpler.
How do you cancel goroutines with context?h2
In Go, you can cancel goroutines using the context
package by passing a context.Context
that signals cancellation. The context.WithCancel
, context.WithTimeout
, or context.WithDeadline
functions create a cancellable context. Goroutines monitor the context’s Done()
channel to stop execution when cancellation is triggered.
Example:
package main
import ( "context" "fmt" "time")
func worker(ctx context.Context) { select { case <-time.After(2 * time.Second): // Simulate work fmt.Println("Worker completed") case <-ctx.Done(): // Check for cancellation fmt.Println("Worker cancelled:", ctx.Err()) }}
func main() { ctx, cancel := context.WithCancel(context.Background()) go worker(ctx) time.Sleep(1 * time.Second) cancel() // Signal cancellation time.Sleep(1 * time.Second) // Wait for worker to exit}
Output: Worker cancelled: context canceled
Key points:
- Use
ctx.Done()
in aselect
to detect cancellation. - Call
cancel()
to signal all goroutines using the context. - Ensure cleanup (e.g., closing resources) when
ctx.Done()
is triggered. - Useful for HTTP requests, timeouts, or user-initiated cancellations.
What is context.WithTimeout?h2
context.WithTimeout
in Go creates a context.Context
that automatically cancels after a specified duration, useful for setting deadlines on operations like HTTP requests or database queries. It’s a wrapper around context.WithCancel
and time.AfterFunc
to trigger cancellation when the timeout expires.
Example:
package main
import ( "context" "fmt" "time")
func worker(ctx context.Context) { select { case <-time.After(2 * time.Second): // Simulate work fmt.Println("Work completed") case <-ctx.Done(): fmt.Println("Timed out:", ctx.Err()) }}
func main() { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() // Ensure cleanup go worker(ctx) time.Sleep(3 * time.Second) // Wait for worker}
Output: Timed out: context deadline exceeded
Key points:
- Returns a context and a
cancel
function. - Cancels automatically after the specified duration.
- Use
ctx.Done()
to detect timeout in goroutines. - Call
cancel()
early to release resources if done before timeout.
How do you propagate values with context?h2
In Go, you can propagate values with the context
package using context.WithValue
to attach key-value pairs to a context. These values are request-scoped and accessible to goroutines handling the same context, making it ideal for passing metadata like user IDs or request IDs across API boundaries.
Example:
package main
import ( "context" "fmt")
func main() { type key string ctx := context.WithValue(context.Background(), key("userID"), "12345")
go func(ctx context.Context) { userID, ok := ctx.Value(key("userID")).(string) if ok { fmt.Println("UserID:", userID) // Output: UserID: 12345 } }(ctx)
// Wait for goroutine time.Sleep(time.Second)}
Key points:
- Use
context.WithValue(parent, key, value)
to create a new context with the value. - Keys should be unexported types (e.g.,
struct{}
or custom type) to avoid collisions. - Access values with
ctx.Value(key)
, using type assertion to retrieve the value. - Avoid overuse; only store request-scoped data, not for passing function arguments.
What is the empty interface?h2
The empty interface in Go, written as interface{}
, is an interface with no methods. Since any type satisfies an interface with zero requirements, interface{}
can hold values of any type, making it useful for generic programming.
Example:
func printValue(v interface{}) { fmt.Println(v)}
func main() { var i interface{} i = 42 // Holds an int printValue(i) // Output: 42 i = "hello" // Holds a string printValue(i) // Output: hello}
Key points:
- Flexibility: Accepts any type, like a void pointer in C.
- Type assertion/switch: Use
v.(type)
or type switches to access the underlying type. - Use cases: JSON parsing, generic functions, or collections of mixed types.
- Caution: Sacrifices type safety; overuse can lead to runtime errors.
Use interface{}
sparingly, preferring specific interfaces or generics (Go 1.18+) for type safety.
How do you use interface{}
for generics-like behavior?h2
In Go, interface{}
enables generics-like behavior by allowing functions or data structures to handle values of any type, as it has no method requirements. However, it requires type assertions or type switches to access the underlying value, sacrificing type safety. Since Go 1.18, generics are preferred, but interface{}
is still used in legacy code or for dynamic types.
Example:
func printAnything(v interface{}) { switch val := v.(type) { case string: fmt.Println("String:", val) case int: fmt.Println("Int:", val) default: fmt.Println("Unknown:", val) }}
func main() { printAnything("hello") // Output: String: hello printAnything(42) // Output: Int: 42}
Key points:
- Use
interface{}
to store or pass any type (e.g., in slices, maps, or function arguments). - Access values with type assertions (
v.(string)
) or type switches. - Common in JSON parsing or flexible APIs.
- Drawbacks: Runtime type errors, less safe than generics.
Prefer generics for modern Go code when type constraints are known.
What are error wrappers?h2
Error wrappers in Go are custom error types that encapsulate an underlying error while adding additional context or metadata. They implement the error
interface and typically wrap another error to provide more details, such as stack traces or specific error codes, while preserving the original error for inspection. This is commonly done using the errors
package (since Go 1.13) or libraries like github.com/pkg/errors
.
Example:
package main
import ( "errors" "fmt")
type CustomError struct { msg string err error}
func (e *CustomError) Error() string { return fmt.Sprintf("%s: %v", e.msg, e.err)}
func doSomething() error { return &CustomError{msg: "failed operation", err: errors.New("database error")}}
func main() { err := doSomething() fmt.Println(err) // Output: failed operation: database error}
Key points:
- Use
errors.Wrap
(frompkg/errors
) or custom structs to wrap errors. - Enables richer error messages and context.
- Use with
errors.Is
anderrors.As
to check or extract wrapped errors. - Enhances debugging without losing original error information.
How do you use errors.Is and errors.As?h2
In Go, errors.Is
and errors.As
(introduced in Go 1.13) inspect wrapped errors to check their type or extract values. They work with errors wrapped using fmt.Errorf
with %w
or libraries like pkg/errors
.
-
errors.Is: Checks if an error or any wrapped error matches a specific error value.
Go err := fmt.Errorf("wrapped: %w", errors.New("target error"))if errors.Is(err, errors.New("target error")) {fmt.Println("Found target error")} -
errors.As: Extracts the first error in the chain that matches a target type into a variable.
Go type CustomError struct{ msg string }func (e *CustomError) Error() string { return e.msg }err := fmt.Errorf("wrapped: %w", &CustomError{msg: "custom"})var ce *CustomErrorif errors.As(err, &ce) {fmt.Println("Custom error:", ce.msg) // Output: Custom error: custom}
Key points:
- Use
errors.Is
for comparing specific error values (e.g.,io.EOF
). - Use
errors.As
to extract typed errors for further inspection. - Both traverse the error chain, supporting wrapped errors.
What is table-driven testing?h2
Table-driven testing in Go is a pattern where test cases are defined in a slice or array of structs, each containing input data and expected outputs. This approach organizes multiple test scenarios in a table-like structure, making tests concise, maintainable, and easy to extend.
Example:
package main
import "testing"
func Add(a, b int) int { return a + b }
func TestAdd(t *testing.T) { tests := []struct { a, b, want int }{ {1, 2, 3}, {0, 0, 0}, {-1, 1, 0}, } for _, tt := range tests { t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) { if got := Add(tt.a, tt.b); got != tt.want { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) } }) }}
Key points:
- Define test cases as structs with inputs and expected results.
- Loop through the table to run tests.
- Use
t.Run
for named subtests, improving readability and debugging. - Ideal for testing functions with multiple input-output combinations.
How do you mock dependencies in tests?h2
In Go, mocking dependencies in tests is typically done by defining interfaces for dependencies, creating mock implementations, and injecting them into the code under test. This approach leverages Go’s implicit interface satisfaction for flexibility.
Example:
package main
import "testing"
// Dependency interfacetype DataStore interface { Get(id int) string}
// Mock implementationtype MockStore struct { data map[int]string}
func (m *MockStore) Get(id int) string { return m.data[id]}
// Function to testfunc GetData(ds DataStore, id int) string { return ds.Get(id)}
// Test with mockfunc TestGetData(t *testing.T) { mock := &MockStore{data: map[int]string{1: "test"}} result := GetData(mock, 1) if result != "test" { t.Errorf("GetData = %s; want test", result) }}
Key points:
- Define an interface for the dependency (e.g.,
DataStore
). - Create a mock struct implementing the interface with controlled behavior.
- Pass the mock to the function during testing.
- Use libraries like
testify/mock
for complex mocks. - Ensures isolation and predictable test outcomes.
What is the httptest package?h2
The httptest
package in Go provides utilities for testing HTTP servers and clients. It simplifies creating mock HTTP servers, generating test requests, and capturing responses, making it ideal for unit testing net/http
-based code without network dependencies.
Key components:
- httptest.NewServer: Creates a test HTTP server with a given handler.
- httptest.NewRequest: Constructs an
http.Request
for testing. - httptest.ResponseRecorder: Records the response from a handler for inspection.
Example:
package main
import ( "net/http" "net/http/httptest" "testing")
func Handler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello"))}
func TestHandler(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) w := httptest.NewResponseRecorder() Handler(w, req) if w.Body.String() != "Hello" { t.Errorf("got %s, want Hello", w.Body.String()) }}
Key points:
- No real network calls, ensuring fast and reliable tests.
- Use
ResponseRecorder
to check status codes, headers, or body. - Ideal for testing handlers, middleware, or API endpoints.
How do you test HTTP handlers?h2
In Go, you test HTTP handlers using the net/http/httptest
package, which provides tools to simulate HTTP requests and capture responses without a real server. Use httptest.NewRequest
to create test requests and httptest.ResponseRecorder
to record handler responses for verification.
Example:
package main
import ( "net/http" "net/http/httptest" "testing")
func MyHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("Hello, World!"))}
func TestMyHandler(t *testing.T) { req := httptest.NewRequest("GET", "/test", nil) w := httptest.ResponseRecorder() MyHandler(w, req)
if w.Code != http.StatusOK { t.Errorf("got status %d, want %d", w.Code, http.StatusOK) } if got := w.Body.String(); got != "Hello, World!" { t.Errorf("got body %s, want Hello, World!", got) }}
Key points:
- Create a request with
httptest.NewRequest(method, url, body)
. - Use
httptest.ResponseRecorder
to capture status, headers, and body. - Verify response details (e.g.,
w.Code
,w.Body.String()
). - For complex routing, test with
httptest.NewServer
. - Ensures isolated, repeatable tests without network dependencies.
Explain deep equality with reflect.DeepEqual.h2
In Go, reflect.DeepEqual
is a function from the reflect
package that compares two values for deep equality, checking if they are structurally identical, including nested fields, slices, maps, and other complex types. Unlike the ==
operator, which checks for reference equality or simple value comparison, DeepEqual
recursively examines the entire structure of the values.
Example:
package main
import ( "fmt" "reflect")
func main() { a := []int{1, 2, 3} b := []int{1, 2, 3} fmt.Println(reflect.DeepEqual(a, b)) // Output: true
m1 := map[string]int{"x": 1} m2 := map[string]int{"x": 1} fmt.Println(reflect.DeepEqual(m1, m2)) // Output: true
fmt.Println(reflect.DeepEqual([]int{1, 2}, []int{2, 1})) // Output: false}
Key points:
- Compares slices, maps, structs, arrays, and nested types recursively.
- Handles
nil
values, pointers, and interfaces. - Useful in tests to compare complex data structures.
- Caveats: Slow due to reflection; sensitive to unexported fields (ignored in structs) and pointer addresses.
Use reflect.DeepEqual
in testing (e.g., with testing.T
) or when comparing dynamic types, but avoid in performance-critical code.
What are function literals?h2
Function literals in Go are anonymous functions defined inline without a named identifier. They are used to create functions on the fly, often for short-lived tasks, closures, or passing as arguments. They have the same syntax as regular functions but lack a name and are typically assigned to variables or passed directly.
Example:
package main
import "fmt"
func main() { // Function literal assigned to a variable add := func(a, b int) int { return a + b } fmt.Println(add(2, 3)) // Output: 5
// Function literal passed directly func(x int) { fmt.Println("Square:", x*x) }(4) // Output: Square: 16}
Key points:
- Syntax:
func(parameters) returnType { body }
. - Often used in goroutines, callbacks, or deferred execution.
- Enable closures by capturing surrounding variables.
- Useful for concise, one-off logic without declaring named functions.
Function literals enhance flexibility in functional programming patterns.
How do you capture variables in closures?h2
In Go, closures are created using function literals that capture variables from their surrounding scope. These variables are accessible within the closure even after the outer function returns, as the closure retains a reference to them. Captured variables are shared, so modifications affect the original variable across all closures referencing it.
Example:
package main
import "fmt"
func counter() func() int { count := 0 // Captured variable return func() int { count++ // Modifies shared variable return count }}
func main() { c1 := counter() c2 := counter() fmt.Println(c1()) // Output: 1 fmt.Println(c1()) // Output: 2 fmt.Println(c2()) // Output: 1 (separate instance)}
Key points:
- Variables are captured by reference, not value, so changes persist.
- Each closure instance (e.g.,
c1
,c2
) captures its own copy of variables from separate outer function calls. - Common in iterators, callbacks, or stateful functions.
- Be cautious of unintended side effects due to shared state in concurrent code.
Use closures for stateful or context-dependent logic.
What is data race?h2
A data race in Go occurs when two or more goroutines access the same memory location concurrently, with at least one performing a write, and there’s no synchronization (e.g., mutexes or channels). This leads to unpredictable behavior, as the outcome depends on the goroutines’ execution order.
Example of a data race:
package main
import ( "fmt" "time")
func main() { var counter int go func() { counter++ }() go func() { counter++ }() time.Sleep(time.Second) fmt.Println(counter) // Output: unpredictable (e.g., 1 or 2)}
Key points:
- Data races cause undefined behavior, like corrupted data or crashes.
- Detect using the race detector:
go run -race main.go
. - Prevent with synchronization:
- Mutexes: Use
sync.Mutex
to lock shared resources. - Channels: Coordinate data access safely.
- Atomic operations: Use
sync/atomic
for simple variables.
- Mutexes: Use
- Common in concurrent programs without proper coordination.
Avoid data races by ensuring synchronized access to shared variables.
How do you avoid data races?h2
To avoid data races in Go, where multiple goroutines access shared memory concurrently with at least one write, use synchronization mechanisms to ensure safe access. Here are key strategies:
-
Channels: Prefer channels for communication between goroutines, ensuring safe data transfer without shared memory.
Go ch := make(chan int)go func() { ch <- 42 }()result := <-ch -
Mutexes: Use
sync.Mutex
orsync.RWMutex
to lock shared resources during access.Go var mu sync.Mutexvar counter intmu.Lock()counter++mu.Unlock() -
Atomic Operations: Use
sync/atomic
for simple, lock-free updates (e.g., counters).Go var counter int32atomic.AddInt32(&counter, 1) -
Avoid Shared State: Pass data via channels or function arguments instead of sharing variables.
-
Race Detector: Run
go test -race
orgo run -race
to detect races during development.
These methods ensure predictable, thread-safe concurrent execution.
Explain HTTP routing with net/http.h2
HTTP routing in Go’s net/http
package maps URL paths and HTTP methods to handler functions, determining how incoming requests are processed. The package provides a default multiplexer, http.ServeMux
, to handle routing.
Key points:
- ServeMux: A request multiplexer that matches URL patterns to registered handlers.
- Registering routes: Use
http.Handle
orhttp.HandleFunc
to map a path to a handler. - Handlers: Functions implementing
http.Handler
orhttp.HandlerFunc
process requests.
Example:
package main
import ( "fmt" "net/http")
func main() { mux := http.NewServeMux() mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, World!") }) mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "User Page") }) http.ListenAndServe(":8080", mux)}
- Access
http://localhost:8080/hello
to see “Hello, World!”. - Patterns can include exact paths or prefixes (e.g.,
/user/
matches/user/123
). - Method-specific routing requires manual checks (e.g.,
r.Method == "GET"
). - For advanced routing (e.g., parameters), use libraries like
gorilla/mux
.
ServeMux
is simple, concurrent-safe, and sufficient for basic routing needs.
What is the http.Handler interface?h2
The http.Handler
interface in Go’s net/http
package defines a single method, ServeHTTP(http.ResponseWriter, *http.Request)
, that handles HTTP requests. Any type implementing this method can be used as an HTTP handler, making it the core mechanism for processing requests in Go web servers.
Example:
package main
import ( "fmt" "net/http")
type MyHandler struct{}
func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello from MyHandler!")}
func main() { handler := MyHandler{} http.Handle("/myroute", handler) http.ListenAndServe(":8080", nil)}
Key points:
ServeHTTP
processes the request (r
) and writes the response usingw
.- Use
http.Handle
to register handlers with aServeMux
. http.HandlerFunc
is a convenience type to use functions as handlers.- Enables custom handlers for middleware, routing, or specialized logic.
This interface provides flexibility for building modular, reusable HTTP servers.
How do you handle form data?h2
In Go, form data from HTTP requests is handled using the net/http
package. The http.Request
struct provides methods to access form data sent via POST
, PUT
, or PATCH
requests.
Example:
package main
import ( "fmt" "net/http")
func handler(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, "Error parsing form", http.StatusBadRequest) return } name := r.FormValue("name") // Access form field fmt.Fprintf(w, "Hello, %s!", name)}
func main() { http.HandleFunc("/submit", handler) http.ListenAndServe(":8080", nil)}
Key points:
- Call
r.ParseForm()
to parse form data intor.Form
(forapplication/x-www-form-urlencoded
) orr.PostForm
(for POST data). - Use
r.FormValue(key)
to get a single value for a key. - For file uploads (
multipart/form-data
), user.ParseMultipartForm(maxMemory)
and accessr.MultipartForm
. - Always validate and sanitize input to prevent errors or security issues.
- Respond with
http.Error
for invalid requests.
This approach is simple and secure for handling form submissions.
What is JSON unmarshaling?h2
JSON unmarshaling in Go is the process of converting JSON data (typically a byte slice) into a Go data structure, such as a struct, map, or slice, using the encoding/json
package. The json.Unmarshal
function parses the JSON and populates the target variable, matching JSON keys to struct fields or map keys.
Example:
package main
import ( "encoding/json" "fmt")
type Person struct { Name string `json:"name"` Age int `json:"age"`}
func main() { jsonData := []byte(`{"name":"Alice","age":25}`) var p Person err := json.Unmarshal(jsonData, &p) if err != nil { fmt.Println("Error:", err) return } fmt.Printf("Name: %s, Age: %d\n", p.Name, p.Age) // Output: Name: Alice, Age: 25}
Key points:
json.Unmarshal(data []byte, v interface{})
requires a pointer to the target (e.g.,&p
).- Struct tags (e.g.,
json:"name"
) map JSON keys to fields. - Handles nested structures, arrays, and maps.
- Errors occur for invalid JSON or type mismatches.
- Use for parsing API responses or configuration files.
How do you customize JSON tags?h2
In Go, JSON tags are customized using struct field tags in the encoding/json
package to control how struct fields are marshaled to or unmarshaled from JSON. Tags are defined in backticks after the field, with the format json:"key,options"
.
Example:
package main
import ( "encoding/json" "fmt")
type Person struct { Name string `json:"full_name"` // Rename field Age int `json:"age,omitempty"` // Omit if empty Pass string `json:"-"` // Ignore field}
func main() { p := Person{Name: "Alice", Pass: "secret"} data, _ := json.Marshal(p) fmt.Println(string(data)) // Output: {"full_name":"Alice","age":0}}
Key points:
- Rename:
json:"new_name"
maps a field to a custom JSON key. - Omit empty:
json:"key,omitempty"
excludes zero-value fields. - Ignore:
json:"-"
skips a field during marshaling/unmarshaling. - Tags are case-sensitive and must match JSON keys exactly.
- Use for API compatibility or cleaner JSON output.
This ensures flexible JSON serialization tailored to your needs.
What is sql.DB?h2
The sql.DB
type in Go’s database/sql
package represents a database connection pool, managing a set of connections to a database. It provides a high-level interface for executing SQL queries, managing transactions, and handling concurrent access in a thread-safe way. It’s not a single connection but a pool that reuses connections for efficiency.
Key features:
- Connection pooling: Automatically manages multiple database connections, reusing them for queries.
- Thread-safe: Safe for use across goroutines without explicit locking.
- Methods: Supports
Query
,QueryRow
,Exec
,Prepare
, andBegin
for database operations.
Example:
package main
import ( "database/sql" _ "github.com/go-sql-driver/mysql" "log")
func main() { db, err := sql.Open("mysql", "user:password@/dbname") if err != nil { log.Fatal(err) } defer db.Close() // Closes pool err = db.Ping() // Test connection if err != nil { log.Fatal(err) }}
Use sql.DB
for database interactions, ensuring proper resource cleanup with defer db.Close()
.
How do you prepare statements?h2
In Go, prepared statements are created using the database/sql
package’s DB.Prepare
or Tx.Prepare
methods to compile an SQL statement once and execute it multiple times with different parameters. This improves performance and security by preventing SQL injection.
Example:
package main
import ( "database/sql" _ "github.com/go-sql-driver/mysql" "log")
func main() { db, err := sql.Open("mysql", "user:password@/dbname") if err != nil { log.Fatal(err) } defer db.Close()
// Prepare statement stmt, err := db.Prepare("INSERT INTO users(name, age) VALUES(?, ?)") if err != nil { log.Fatal(err) } defer stmt.Close()
// Execute with parameters _, err = stmt.Exec("Alice", 25) if err != nil { log.Fatal(err) }}
Key points:
- Use
?
placeholders for parameters (driver-specific syntax). stmt.Exec
orstmt.Query
executes the statement with values.- Prepared statements are reusable and safe for concurrent use.
- Close with
stmt.Close()
to free resources. - Ideal for repeated queries or user input.
What is sql.Tx for transactions?h2
The sql.Tx
type in Go’s database/sql
package represents a database transaction, allowing multiple SQL operations to be executed as a single, atomic unit. It ensures that either all operations succeed (committed) or none are applied (rolled back), maintaining database consistency. Transactions are created using DB.Begin
or DB.BeginTx
.
Example:
package main
import ( "database/sql" _ "github.com/go-sql-driver/mysql" "log")
func main() { db, err := sql.Open("mysql", "user:password@/dbname") if err != nil { log.Fatal(err) } defer db.Close()
tx, err := db.Begin() if err != nil { log.Fatal(err) }
_, err = tx.Exec("INSERT INTO users(name) VALUES(?)", "Alice") if err != nil { tx.Rollback() // Undo on error log.Fatal(err) }
_, err = tx.Exec("UPDATE users SET age = ? WHERE name = ?", 25, "Alice") if err != nil { tx.Rollback() log.Fatal(err) }
err = tx.Commit() // Apply changes if err != nil { log.Fatal(err) }}
Key points:
- Begin:
db.Begin()
ordb.BeginTx(ctx, opts)
starts a transaction. - Operations: Use
tx.Exec
,tx.Query
, ortx.Prepare
for queries within the transaction. - Commit/Rollback: Call
tx.Commit()
to save changes ortx.Rollback()
to discard them. - Concurrency: Transactions are not thread-safe; use in a single goroutine.
- Use case: Ensures atomicity for multi-step database operations (e.g., bank transfers).
Use sql.Tx
for operations requiring consistency, ensuring proper error handling and cleanup.
How do you use go mod init?h2
The go mod init
command in Go initializes a new module, creating a go.mod
file to manage dependencies and versioning. It’s the first step in setting up a Go project with Go Modules, specifying the module’s import path.
Usage:
go mod init <module-path>
<module-path>
: Typically the module’s repository path (e.g.,github.com/user/project
).
Example:
go mod init example.com/myapp
This creates a go.mod
file:
module example.com/myapp
go 1.20
Key points:
- Run
go mod init
in your project’s root directory. - The module path should be unique, often based on a repository URL.
go.mod
tracks the Go version and dependencies.- After initialization, use
go get
to add dependencies, which updatesgo.mod
. - If no module path is provided, Go attempts to infer it (e.g., from VCS).
Use go mod init
to start a new module or convert an existing project to use Go Modules.
What is the go.sum file?h2
The go.sum
file in Go is a generated file used by Go Modules to ensure dependency integrity and reproducibility. It records cryptographic hashes of module versions used in a project, verifying that the same dependency versions are fetched consistently.
Key points:
- Purpose: Stores SHA-256 hashes of module content and versions to prevent tampering or corruption.
- Format: Each line lists a module, its version, and hashes (e.g.,
module/version h1:hash
). - Generation: Created/updated by commands like
go mod init
,go get
, orgo mod tidy
. - Usage: Go uses
go.sum
to verify downloaded modules match expected hashes.
Example:
github.com/some/package v1.2.3 h1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=github.com/some/package v1.2.3/go.mod h1:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy=
Notes:
- Don’t edit manually; it’s managed by Go tools.
- Safe to commit to version control for reproducible builds.
- Missing or mismatched hashes cause build errors unless resolved with
go mod verify
.
Use go.sum
to ensure secure and consistent dependency management.
How do you replace modules?h2
In Go, you can replace modules in a project using the replace
directive in the go.mod
file. This allows you to substitute a module with a different version, fork, or local path, useful for testing, debugging, or using unpublished changes.
Steps:
- Edit the
go.mod
file. - Add a
replace
directive with the format:replace module-path => replacement-path
.
Example: To replace a module with a local version:
module example.com/myapp
require github.com/some/package v1.2.3
replace github.com/some/package v1.2.3 => ../local/package
Or to replace with a specific version or fork:
replace github.com/some/package v1.2.3 => github.com/other/package v1.4.0
Key points:
- Run
go mod tidy
to updatego.sum
after addingreplace
. - Local paths are relative to the project root.
- Useful for testing forks, local changes, or unavailable modules.
- Affects only the current module; transitive dependencies need separate
replace
directives.
Use go build
or go run
to apply the replacement.
What are build constraints?h2
Build constraints in Go, also known as build tags, are special comments that control whether a Go file is included in a build based on conditions like operating system, architecture, or custom tags. They allow conditional compilation for platform-specific code or feature toggling.
Syntax:
// +build tag1,tag2
Example:
// +build linux
package main
import "fmt"
func main() { fmt.Println("Linux-specific code")}
Another file:
// +build windows
package main
import "fmt"
func main() { fmt.Println("Windows-specific code")}
Usage:
go build -tags linux
Key points:
- Tags like
linux
,windows
,amd64
, or custom tags (e.g.,dev
) are specified with// +build
. - Use
!tag
to exclude (e.g.,// +build !windows
). - Multiple tags with commas mean AND; separate lines mean OR.
- Run
go build -tags "tag1 tag2"
to specify active tags. - Common for cross-platform code or enabling/disabling features.
Build constraints ensure modular, platform-specific builds without runtime checks.
How do you compile for different OS?h2
In Go, cross-compiling for different operating systems is done by setting the GOOS
environment variable to specify the target operating system (e.g., linux
, windows
, darwin
) during the go build
command. Optionally, set GOARCH
for the target architecture (e.g., amd64
, arm
).
Example (compile for Windows on Linux):
GOOS=windows GOARCH=amd64 go build -o myapp.exe
Steps:
- Identify the target
GOOS
andGOARCH
(rungo env GOOS GOARCH
for supported values). - Run
go build
withGOOS
andGOARCH
set:Terminal window GOOS=linux GOARCH=arm go build -o myapp - Use
-o
to name the output binary (e.g.,.exe
for Windows).
Key points:
- No external tools needed; Go’s toolchain supports cross-compilation natively.
- Ensure dependencies are compatible with the target platform.
- Example: Compile for macOS (
darwin
) on Linux:Terminal window GOOS=darwin GOARCH=amd64 go build -o myapp - Useful for building binaries for servers, containers, or other platforms.
This ensures the binary is compatible with the target OS without rebuilding.
What is pointer arithmetic with unsafe?h2
Pointer arithmetic in Go’s unsafe
package allows low-level manipulation of memory addresses, bypassing Go’s type safety. Using unsafe.Pointer
, you can perform operations like incrementing or offsetting pointers to access memory locations directly, similar to C. This is typically used for performance-critical code or interfacing with C libraries.
Example:
package main
import ( "fmt" "unsafe")
func main() { arr := []int{1, 2, 3} ptr := unsafe.Pointer(&arr[0]) // Get pointer to first element size := unsafe.Sizeof(arr[0]) // Size of int
// Access second element via pointer arithmetic ptr2 := unsafe.Pointer(uintptr(ptr) + size) fmt.Println(*(*int)(ptr2)) // Output: 2}
Key points:
- Convert
unsafe.Pointer
touintptr
for arithmetic (e.g.,uintptr(ptr) + offset
). - Cast back to a typed pointer (e.g.,
*int
) to dereference. - Use cases: Interfacing with C code, optimizing memory access, or low-level data manipulation.
- Caution: Risks memory corruption, undefined behavior, or crashes; avoid unless necessary.
Use standard Go for safety; unsafe
is for rare, specialized scenarios.
How do you convert types with unsafe?h2
In Go, the unsafe
package allows type conversions that bypass the type system by using unsafe.Pointer
. This enables casting a value of one type to another, even if they are incompatible, by converting through a memory address. It’s typically used for low-level operations or C interoperability but is risky due to potential undefined behavior.
Example:
package main
import ( "fmt" "unsafe")
func main() { i := int32(42) ptr := unsafe.Pointer(&i) // Convert to unsafe.Pointer f := (*float32)(ptr) // Cast to *float32 fmt.Println(*f) // Output: interprets int32 bits as float32}
Key points:
- Use
unsafe.Pointer
as an intermediary to cast between types (e.g.,int32
tofloat32
). - Requires a pointer to the source value and casting to a pointer of the target type.
- Use cases: Interfacing with C libraries, reinterpreting memory layouts, or low-level optimizations.
- Caution: Can cause undefined behavior if types are incompatible or memory is misinterpreted.
Avoid unsafe
unless absolutely necessary, as it breaks Go’s type safety.
What is the log package?h2
The log
package in Go provides a simple logging framework for writing log messages to an output, typically os.Stderr
. It’s used for debugging, monitoring, or recording application events, offering basic functionality like timestamps, prefixes, and customizable output.
Key features:
- Default logger: Outputs messages with
log.Print
,log.Printf
, orlog.Println
. - Fatal logging:
log.Fatal
logs and exits with status 1;log.Panic
logs and panics. - Custom loggers: Create with
log.New(writer, prefix, flags)
to customize output.
Example:
package main
import ( "log" "os")
func main() { log.Println("Starting application") // Output: 2025/09/30 19:29:00 Starting application
// Custom logger logger := log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime) logger.Println("Custom log message")}
Key points:
- Default format includes date/time (e.g.,
2009/11/10 23:00:00
). - Flags like
log.Ldate
,log.Ltime
, orlog.Lshortfile
customize output. - Thread-safe for concurrent use.
- Use for simple logging; for advanced needs, consider
zap
orlogrus
.
The log
package is lightweight and built into Go’s standard library.
How do you configure logging?h2
In Go, the log
package allows logging configuration by creating a custom logger with log.New
, specifying the output destination, prefix, and flags. This customizes log format, output location, and additional details like timestamps or file locations.
Example:
package main
import ( "log" "os")
func main() { // Create custom logger logger := log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile) logger.Println("Custom log message") // Output: INFO: 2025/09/30 19:30:00 main.go:10: Custom log message
// Change default logger settings log.SetPrefix("APP: ") log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) log.SetOutput(os.Stderr) log.Println("Default logger message")}
Key configuration options:
- Output: Use
log.New(writer, ...)
orlog.SetOutput(writer)
to set output (e.g.,os.Stdout
, file, orio.MultiWriter
). - Prefix: Set with
log.New(..., prefix, ...)
orlog.SetPrefix("prefix")
for log message prefixes. - Flags: Control format with flags like
log.Ldate
,log.Ltime
,log.Lmicroseconds
,log.Lshortfile
, orlog.Llongfile
. - Custom logger:
log.New
creates a logger independent of the default one.
Key points:
- Thread-safe for concurrent use.
- For advanced logging (structured, leveled), use libraries like
zap
orlogrus
. - Always handle file resources (e.g., close files if writing to them).
This allows tailored logging for debugging or monitoring.
What is the regexp package?h2
The regexp
package in Go provides regular expression functionality for pattern matching, searching, and replacing in strings. It supports Perl-compatible regular expressions to identify or manipulate substrings based on patterns.
Key features:
- Compile: Create a
Regexp
object from a pattern withregexp.Compile
orregexp.MustCompile
. - Methods: Includes
MatchString
,FindString
,ReplaceAllString
, and more for matching and manipulation. - Submatches: Extract matched groups using
FindStringSubmatch
.
Example:
package main
import ( "fmt" "regexp")
func main() { re, err := regexp.Compile(`\d+`) if err != nil { fmt.Println("Error:", err) return } match := re.MatchString("abc123") // Check if pattern matches fmt.Println(match) // Output: true found := re.FindString("abc123") // Find first match fmt.Println(found) // Output: 123}
Key points:
- Use
MustCompile
for patterns that must not fail (panics on error). - Thread-safe for read-only use after compilation.
- Common for parsing, validation (e.g., emails), or text processing.
- Performance tip: Compile once, reuse the
Regexp
object.
Use regexp
for string pattern tasks, but validate patterns to avoid errors.
How do you compile a regex?h2
In Go, you compile a regular expression using the regexp
package with either regexp.Compile
or regexp.MustCompile
. These functions create a Regexp
object from a pattern string, which can then be used for matching, searching, or replacing.
Example:
package main
import ( "fmt" "regexp")
func main() { // Compile with error handling re, err := regexp.Compile(`\d+`) // Matches one or more digits if err != nil { fmt.Println("Error:", err) return } fmt.Println(re.MatchString("abc123")) // Output: true
// MustCompile (panics on error) re2 := regexp.MustCompile(`[a-z]+`) // Matches one or more lowercase letters fmt.Println(re2.FindString("Hello123")) // Output: ello}
Key points:
regexp.Compile(pattern)
returns aRegexp
and an error; use for error handling.regexp.MustCompile(pattern)
panics on invalid patterns; use for known-valid patterns.- Compiled
Regexp
is reusable and thread-safe for read-only operations. - Store the
Regexp
object to avoid recompiling for performance. - Common use: Validate input, extract data, or transform strings.
Compile once and reuse for efficient pattern matching.
What is the template package?h2
The template
package in Go, specifically text/template
, provides a templating engine for generating dynamic text output, such as HTML, configuration files, or other formatted strings. It allows you to define templates with placeholders and logic, which are populated with data at runtime.
Key features:
- Placeholders: Use
{{.Field}}
to insert data from structs or maps. - Logic: Supports conditionals (
{{if}}
), loops ({{range}}
), and variables. - Functions: Custom or built-in functions (e.g.,
printf
,len
) for formatting. - Safe escaping: Prevents injection by escaping output (use
html/template
for HTML).
Example:
package main
import ( "os" "text/template")
type Person struct { Name string}
func main() { t, _ := template.New("greeting").Parse("Hello, {{.Name}}!\n") p := Person{Name: "Alice"} t.Execute(os.Stdout, p) // Output: Hello, Alice!}
Key points:
- Create templates with
template.New
and parse withParse
orParseFiles
. - Execute with
Execute(w io.Writer, data interface{})
. - Use
html/template
for HTML to ensure safe escaping. - Ideal for generating reports, web pages, or configuration files.
Use text/template
for text-based templating, ensuring proper error handling for parsing.
How do you parse templates?h2
In Go, templates are parsed using the text/template
or html/template
package with methods like Parse
, ParseFiles
, or ParseGlob
. Parsing converts a template string or file into a template.Template
object, which can then be executed with data to generate output.
Example (Parsing a template string):
package main
import ( "os" "text/template")
type Person struct { Name string}
func main() { // Parse template string tmpl, err := template.New("greeting").Parse("Hello, {{.Name}}!\n") if err != nil { panic(err) } p := Person{Name: "Alice"} tmpl.Execute(os.Stdout, p) // Output: Hello, Alice!}
Example (Parsing a file):
// greeting.tmpl: Hello, {{.Name}}!package main
import ( "os" "text/template")
func main() { tmpl, err := template.ParseFiles("greeting.tmpl") if err != nil { panic(err) } p := Person{Name: "Bob"} tmpl.Execute(os.Stdout, p)}
Key points:
template.New(name)
creates a new template with a name.Parse(string)
parses a template string;ParseFiles(filenames...)
parses files.ParseGlob(pattern)
parses multiple files matching a pattern (e.g.,*.tmpl
).- Check errors from parsing to catch syntax issues.
- Use
html/template
for HTML to ensure safe escaping.
Parse templates once and reuse the Template
object for efficiency.
What is html/template for safety?h2
The html/template
package in Go is a specialized version of text/template
designed for generating HTML output safely. It automatically escapes data to prevent injection attacks, such as cross-site scripting (XSS), by encoding special characters in HTML, JavaScript, CSS, or URLs based on the context.
Key safety features:
- Auto-escaping: Escapes data in
{{.Field}}
to prevent malicious code execution (e.g.,<script>
becomes<script>
). - Contextual escaping: Applies correct escaping for HTML content, attributes, JavaScript, or URLs.
- Trusted types: Use
template.HTML
,template.JS
, ortemplate.CSS
to mark trusted content, bypassing escaping (use cautiously).
Example:
package main
import ( "html/template" "os")
type Data struct { Content string}
func main() { tmpl, _ := template.New("test").Parse(`<div>{{.Content}}</div>`) data := Data{Content: "<script>alert('XSS')</script>"} tmpl.Execute(os.Stdout, data) // Output: <div><script>alert('XSS')</script></div>}
Key points:
- Use
html/template
for web applications to generate safe HTML. - Avoid
text/template
for HTML, as it doesn’t escape output. - Check parsing errors and validate trusted content to ensure security.
- Ideal for rendering web pages or API responses with user input.
This package ensures safe HTML generation by mitigating injection risks.
How do you execute templates?h2
In Go, templates are executed using the Execute
or ExecuteTemplate
methods from the text/template
or html/template
package. These methods render a parsed template by combining it with data and writing the output to an io.Writer
, such as os.Stdout
or an HTTP response.
Example:
package main
import ( "html/template" "os")
type Person struct { Name string}
func main() { tmpl, err := template.New("greeting").Parse("Hello, {{.Name}}!") if err != nil { panic(err) } p := Person{Name: "Alice"} err = tmpl.Execute(os.Stdout, p) // Output: Hello, Alice! if err != nil { panic(err) }}
Key points:
- Execute:
tmpl.Execute(w io.Writer, data interface{})
renders the template with the provided data. - ExecuteTemplate: Use for named templates in a set:
tmpl.ExecuteTemplate(w, "name", data)
. - Pass a pointer to a struct or a map as
data
to populate placeholders (e.g.,{{.Name}}
). - Check errors from
Execute
for rendering issues. - Use
html/template
for HTML to ensure safe escaping;text/template
for plain text.
Execute templates after parsing, reusing the Template
object for efficiency.
What is the crypto package?h2
The crypto
package in Go is a collection of cryptographic primitives in the standard library, providing tools for secure data handling, such as hashing, encryption, digital signatures, and random number generation. It’s designed for building secure applications, like authentication or data integrity.
Key subpackages:
crypto/sha256
,crypto/md5
: Hashing algorithms (e.g., SHA-256, MD5).crypto/aes
,crypto/rsa
: Encryption/decryption (AES, RSA).crypto/rand
: Cryptographically secure random number generation.crypto/hmac
: Keyed-hash message authentication code.crypto/x509
: Certificate handling for TLS/SSL.
Example (SHA-256 hashing):
package main
import ( "crypto/sha256" "fmt")
func main() { data := []byte("hello") hash := sha256.Sum256(data) fmt.Printf("%x\n", hash) // Output: SHA-256 hash}
Key points:
- Use for secure operations like password hashing, data signing, or encryption.
- Thread-safe for most operations.
- Prefer
crypto/rand
overmath/rand
for security. - Combine with
net/http
for TLS or secure APIs.
Use crypto
for secure, standards-compliant cryptography in applications.
How do you hash passwords?h2
In Go, securely hash passwords using the golang.org/x/crypto/bcrypt
package, which implements the bcrypt algorithm, ideal for password storage due to its adaptive cost and salt inclusion to resist brute-force attacks.
Example:
package main
import ( "fmt" "golang.org/x/crypto/bcrypt")
func main() { password := []byte("myPassword123") // Hash password with default cost hashed, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) if err != nil { fmt.Println("Error:", err) return } fmt.Println("Hashed:", string(hashed))
// Verify password err = bcrypt.CompareHashAndPassword(hashed, []byte("myPassword123")) if err == nil { fmt.Println("Password verified") } else { fmt.Println("Invalid password") }}
Key points:
- Use
bcrypt.GenerateFromPassword
to create a hash with a cost factor (e.g.,bcrypt.DefaultCost
). - Store the hash (includes salt) in the database.
- Use
bcrypt.CompareHashAndPassword
to verify passwords. - Avoid
crypto/sha256
ormd5
for passwords; they’re not secure for this use. - Install:
go get golang.org/x/crypto/bcrypt
.
This ensures secure password storage with resistance to attacks.
What is encoding/base64?h2
The encoding/base64
package in Go provides functions to encode and decode data in Base64 format, a method for representing binary data as an ASCII string. It’s commonly used for encoding binary data (e.g., images, files) in text-based formats like JSON or HTTP payloads.
Key functions:
base64.StdEncoding.EncodeToString(src []byte) string
: Encodes bytes to a Base64 string.base64.StdEncoding.DecodeString(s string) ([]byte, error)
: Decodes a Base64 string to bytes.base64.URLEncoding
: Variant for URL-safe Base64 (uses-
and_
instead of+
and/
).
Example:
package main
import ( "encoding/base64" "fmt")
func main() { data := []byte("Hello, World!") encoded := base64.StdEncoding.EncodeToString(data) fmt.Println("Encoded:", encoded) // Output: Encoded: SGVsbG8sIFdvcmxkIQ==
decoded, err := base64.StdEncoding.DecodeString(encoded) if err != nil { fmt.Println("Error:", err) return } fmt.Println("Decoded:", string(decoded)) // Output: Decoded: Hello, World!}
Key points:
- Thread-safe and simple to use.
- Use
StdEncoding
for general purposes,URLEncoding
for URLs. - Check errors when decoding to handle invalid input.
- Common for API payloads, email attachments, or embedding binary data.
Use encoding/base64
for safe, text-based binary data transfer.
How do you encode data?h2
In Go, encoding data typically involves converting data into a specific format (e.g., Base64, JSON) for storage or transmission. The encoding/base64
package is commonly used for Base64 encoding, while encoding/json
handles JSON. Here’s how to encode data with Base64 as an example:
Example (Base64):
package main
import ( "encoding/base64" "fmt")
func main() { data := []byte("Hello, World!") encoded := base64.StdEncoding.EncodeToString(data) fmt.Println("Encoded:", encoded) // Output: Encoded: SGVsbG8sIFdvcmxkIQ==}
Key points:
- Use
base64.StdEncoding.EncodeToString(src []byte)
for standard Base64 encoding. - For URL-safe Base64, use
base64.URLEncoding
. - For JSON encoding, use
json.Marshal(v interface{})
fromencoding/json
. - Ensure the input is a
[]byte
for Base64 or a compatible type (struct, map) for JSON. - Thread-safe and error-free for valid inputs.
Choose the encoding package based on the target format (e.g., base64
, json
, xml
). For decoding, use corresponding functions like base64.StdEncoding.DecodeString
.
What is the net package?h2
The net
package in Go provides a portable interface for network I/O, including TCP/IP, UDP, and other low-level networking operations. It supports creating servers, clients, and handling connections for various network protocols.
Key features:
- TCP/UDP: Functions like
net.Dial
,net.Listen
for TCP and UDP connections. - Addresses: Types like
net.Addr
,net.TCPAddr
for network addresses. - Connections:
net.Conn
interface for reading/writing data. - DNS: Functions like
net.LookupHost
for resolving domain names.
Example (Simple TCP client):
package main
import ( "fmt" "net")
func main() { conn, err := net.Dial("tcp", "example.com:80") if err != nil { fmt.Println("Error:", err) return } defer conn.Close() fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n") buf := make([]byte, 1024) n, _ := conn.Read(buf) fmt.Println(string(buf[:n]))}
Key points:
- Supports protocols like
tcp
,udp
,unix
(Unix sockets). - Thread-safe for concurrent use.
- Used for low-level networking;
net/http
is preferred for HTTP. - Common for custom servers, clients, or socket programming.
Use net
for building network applications requiring fine-grained control.
How do you create a TCP server?h2
To create a TCP server in Go, use the net
package to listen for incoming connections with net.Listen
and handle them in goroutines for concurrency. Each connection uses the net.Conn
interface for reading and writing data.
Example:
package main
import ( "fmt" "net")
func handleConnection(conn net.Conn) { defer conn.Close() buf := make([]byte, 1024) n, err := conn.Read(buf) if err != nil { fmt.Println("Error reading:", err) return } conn.Write([]byte("Received: " + string(buf[:n])))}
func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { fmt.Println("Error listening:", err) return } defer listener.Close() fmt.Println("Listening on :8080") for { conn, err := listener.Accept() if err != nil { fmt.Println("Error accepting:", err) continue } go handleConnection(conn) // Handle each connection in a goroutine }}
Key points:
net.Listen("tcp", ":port")
starts the server on the specified port.listener.Accept()
waits for incoming connections.- Use goroutines (
go handleConnection
) for concurrent client handling. conn.Read
andconn.Write
manage client communication.- Always defer
conn.Close()
andlistener.Close()
to free resources.
This creates a scalable TCP server for handling multiple clients.
What is UDP in Go?h2
In Go, UDP (User Datagram Protocol) is supported by the net
package for connectionless, lightweight network communication. Unlike TCP, UDP is unreliable, meaning it doesn’t guarantee delivery or order but is faster and suited for applications like streaming or DNS where speed is prioritized over reliability.
Key features:
- Connectionless: No persistent connection; packets (datagrams) are sent independently.
- net.UDPConn: Used for sending/receiving UDP packets.
- Functions:
net.DialUDP
,net.ListenUDP
for client/server communication.
Example (Simple UDP server):
package main
import ( "fmt" "net")
func main() { addr, _ := net.ResolveUDPAddr("udp", ":8080") conn, err := net.ListenUDP("udp", addr) if err != nil { fmt.Println("Error:", err) return } defer conn.Close() buf := make([]byte, 1024) n, addr, _ := conn.ReadFromUDP(buf) fmt.Printf("Received %s from %s\n", buf[:n], addr) conn.WriteToUDP([]byte("Reply"), addr)}
Key points:
- Use
net.ListenUDP
for servers,net.DialUDP
for clients. ReadFromUDP
/WriteToUDP
handle datagrams with source/destination addresses.- Thread-safe for concurrent use.
- Ideal for low-latency, loss-tolerant applications.
Use UDP in Go for fast, non-critical data transfer.
How do you dial connections?h2
In Go, dialing connections establishes a network connection to a remote server using the net
package’s net.Dial
or protocol-specific functions like net.DialTCP
or net.DialUDP
. It connects to a specified network (e.g., TCP, UDP) and address, returning a net.Conn
for communication.
Example (TCP connection):
package main
import ( "fmt" "net")
func main() { conn, err := net.Dial("tcp", "example.com:80") if err != nil { fmt.Println("Error:", err) return } defer conn.Close() fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n") buf := make([]byte, 1024) n, err := conn.Read(buf) if err != nil { fmt.Println("Error reading:", err) return } fmt.Println(string(buf[:n]))}
Key points:
- Syntax:
net.Dial(network, address)
wherenetwork
is"tcp"
,"udp"
, or"unix"
, andaddress
ishost:port
(e.g.,"example.com:80"
). - Returns a
net.Conn
for reading/writing data. - Use
net.DialTimeout
for connections with a timeout. - Always defer
conn.Close()
to release resources. - Thread-safe; use in goroutines for concurrent connections.
Use for clients connecting to servers (e.g., HTTP, databases).
What is the os/exec package?h2
The os/exec
package in Go provides functionality to execute external commands and interact with their input, output, and error streams. It allows running system commands or binaries, capturing their output, and managing their execution.
Key features:
- Run commands:
exec.Command(name, args...)
creates a command to execute. - Output capture: Methods like
Output()
orCombinedOutput()
retrieve stdout/stderr. - Input/Output: Configure
Stdin
,Stdout
, andStderr
for custom I/O handling. - Control: Start, wait, or kill processes.
Example:
package main
import ( "fmt" "os/exec")
func main() { cmd := exec.Command("ls", "-l") output, err := cmd.Output() // Captures stdout if err != nil { fmt.Println("Error:", err) return } fmt.Println(string(output))}
Key points:
- Use
cmd.Run()
for execution without capturing output. - Handle errors, as commands may fail or not exist.
- Thread-safe for creating commands, but manage process lifecycle carefully.
- Common for shell commands, scripting, or invoking tools.
Use os/exec
for integrating external programs or system utilities in Go applications.
How do you run external commands?h2
In Go, you run external commands using the os/exec
package, which allows you to execute system commands or binaries, capture their output, and manage their execution.
Example:
package main
import ( "fmt" "os/exec")
func main() { // Create command cmd := exec.Command("ls", "-l")
// Run and capture output output, err := cmd.Output() // Captures stdout if err != nil { fmt.Println("Error:", err) return } fmt.Println(string(output))}
Key methods:
exec.Command(name, args...)
: Creates a command (e.g.,ls -l
).cmd.Output()
: Runs the command and returns stdout as[]byte
.cmd.Run()
: Executes without capturing output; use for side-effect commands.cmd.CombinedOutput()
: Captures both stdout and stderr.cmd.Start()
andcmd.Wait()
: For manual control or async execution.
Key points:
- Handle errors (e.g., command not found or non-zero exit).
- Set
cmd.Stdin
,cmd.Stdout
,cmd.Stderr
for custom I/O. - Use
cmd.Process.Kill()
to terminate running commands. - Thread-safe for command creation; manage process lifecycle carefully.
Use for invoking tools like git
, grep
, or custom binaries.
What is signal handling?h2
Signal handling in Go involves capturing and responding to operating system signals, such as interrupts (e.g., Ctrl+C) or termination requests, using the os
and os/signal
packages. It allows a program to gracefully handle events like shutdowns or reloads.
Example:
package main
import ( "fmt" "os" "os/signal" "syscall")
func main() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
fmt.Println("Waiting for signals") sig := <-sigs // Blocks until a signal is received fmt.Println("Received signal:", sig)}
Key points:
- Use
signal.Notify(chan, signals...)
to route specified signals to a channel. - Common signals:
syscall.SIGINT
(Ctrl+C),syscall.SIGTERM
(termination). - Read signals from the channel to handle them (e.g., cleanup, shutdown).
- Call
signal.Stop(chan)
to stop capturing signals and close the channel. - Thread-safe; suitable for concurrent programs.
Use signal handling for graceful shutdowns, resource cleanup, or responding to system events in long-running applications.
How do you handle OS signals?h2
In Go, OS signals are handled using the os/signal
package, which captures system signals like SIGINT
(Ctrl+C) or SIGTERM
and routes them to a channel for processing. This enables graceful shutdowns or custom responses to signals in applications.
Example:
package main
import ( "fmt" "os" "os/signal" "syscall")
func main() { // Create a channel for signals sigs := make(chan os.Signal, 1) // Register signals to capture signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
fmt.Println("Running, press Ctrl+C to stop") // Block until a signal is received sig := <-sigs fmt.Println("Received signal:", sig) // Perform cleanup here fmt.Println("Shutting down gracefully")}
Key steps:
- Create a channel (
chan os.Signal
) to receive signals. - Use
signal.Notify(chan, signals...)
to capture specific signals (e.g.,syscall.SIGINT
,syscall.SIGTERM
). - Read from the channel to handle signals, often in a blocking loop or goroutine.
- Call
signal.Stop(chan)
to stop capturing and close the channel when done.
Key points:
- Common signals:
SIGINT
(interrupt),SIGTERM
(termination),SIGHUP
(reload). - Thread-safe for concurrent use.
- Use for cleanup (e.g., closing files, connections) or graceful shutdowns.
- Combine with
context
for coordinated cancellation in complex apps.
This ensures robust handling of system events in servers or long-running programs.
What is the runtime package for debugging?h2
The runtime
package in Go provides low-level facilities for debugging and interacting with the Go runtime system. It includes functions to inspect goroutines, stack traces, memory statistics, and control runtime behavior, making it useful for diagnosing issues in running programs.
Key debugging features:
- Stack traces:
runtime.Stack
captures the current stack trace. - Goroutine inspection:
runtime.NumGoroutine
returns the number of active goroutines. - Breakpoints:
runtime.Breakpoint
triggers a breakpoint for debuggers. - Caller info:
runtime.Caller
retrieves call stack details (file, line number). - Memory stats:
runtime.ReadMemStats
provides memory allocation details.
Example:
package main
import ( "fmt" "runtime")
func main() { fmt.Println("Goroutines:", runtime.NumGoroutine()) // Output: Goroutines: 1 buf := make([]byte, 1<<16) runtime.Stack(buf, true) // Capture stack trace fmt.Println(string(buf))}
Key points:
- Use for low-level debugging, profiling, or diagnostics.
- Thread-safe but avoid overuse in production due to performance impact.
- Combine with tools like
pprof
for advanced debugging. - Common for identifying deadlocks, memory leaks, or goroutine issues.
Use runtime
cautiously for targeted debugging in development or critical scenarios.
How do you get stack traces?h2
In Go, stack traces are obtained using the runtime
package, specifically with functions like runtime.Stack
or runtime.Callers
, to capture details about the call stack for debugging, such as identifying the source of errors or panics. Stack traces include function names, file paths, and line numbers.
Example (using runtime.Stack
):
package main
import ( "fmt" "runtime")
func main() { buf := make([]byte, 1<<16) // Buffer for stack trace n := runtime.Stack(buf, true) // Capture stack for all goroutines fmt.Println(string(buf[:n]))}
Example (using panic/recover):
package main
import ( "fmt" "runtime")
func main() { defer func() { if r := recover(); r != nil { buf := make([]byte, 1<<16) n := runtime.Stack(buf, false) // Capture stack for current goroutine fmt.Printf("Panic: %v\nStack trace:\n%s\n", r, buf[:n]) } }() panic("something went wrong")}
Key points:
runtime.Stack(buf, all)
: Writes stack trace tobuf
;all=true
includes all goroutines,false
for current.runtime.Callers
: Captures program counters for manual stack walking.- Use in
defer
withrecover()
to log stack traces during panics. - Buffer size (e.g.,
1<<16
) must be large enough for the trace. - Useful for debugging errors, deadlocks, or unexpected behavior.
For advanced debugging, use the pprof
package or runtime/debug.PrintStack
.
What is pprof for profiling?h2
The pprof
package in Go, part of runtime/pprof
and net/http/pprof
, is a tool for profiling Go programs to analyze performance, identify bottlenecks, and inspect CPU, memory, or goroutine usage. It generates profiles that can be visualized with tools like go tool pprof
or graphical interfaces.
Key features:
- CPU profiling: Measures where CPU time is spent.
- Memory profiling: Tracks heap allocations to detect memory leaks.
- Goroutine profiling: Inspects active goroutines for concurrency issues.
- Block/Mutex profiling: Identifies contention in locks or I/O.
Example (CPU profiling):
package main
import ( "log" "os" "runtime/pprof")
func main() { f, err := os.Create("cpu.prof") if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile()
// Your code to profile for i := 0; i < 1000000; i++ { _ = i * i }}
Usage:
- Run:
go run main.go
to generatecpu.prof
. - Analyze:
go tool pprof cpu.prof
for interactive analysis orgo tool pprof -http=:8080 cpu.prof
for a web UI.
Key points:
- Enable profiling with
pprof.StartCPUProfile
orpprof.WriteHeapProfile
. - Use
net/http/pprof
for HTTP endpoints (/debug/pprof
) in servers. - Thread-safe; suitable for production with minimal overhead.
- Combine with tools like
graphviz
for visualizations.
Use pprof
to optimize performance-critical applications or debug resource issues.
How do you profile CPU?h2
To profile CPU usage in Go, use the runtime/pprof
package to capture a CPU profile, which records where the program spends its CPU time. This helps identify performance bottlenecks. The profile is saved to a file and analyzed with go tool pprof
.
Example:
package main
import ( "log" "os" "runtime/pprof")
func main() { f, err := os.Create("cpu.prof") if err != nil { log.Fatal(err) } defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil { log.Fatal(err) } defer pprof.StopCPUProfile()
// Code to profile for i := 0; i < 1000000; i++ { _ = i * i }}
Steps:
- Create an output file (e.g.,
cpu.prof
). - Start profiling with
pprof.StartCPUProfile(f)
. - Defer
pprof.StopCPUProfile()
to stop profiling. - Run the program:
go run main.go
. - Analyze:
go tool pprof cpu.prof
orgo tool pprof -http=:8080 cpu.prof
for a web UI.
Key points:
- Captures CPU usage across goroutines.
- Use
go tool pprof
for interactive analysis (e.g.,top
,list
). - Thread-safe, low overhead.
- Ideal for optimizing performance-critical code.
Use CPU profiling to pinpoint slow functions or loops.
What is memory profiling?h2
Memory profiling in Go uses the runtime/pprof
package to analyze heap memory usage, helping identify memory leaks, excessive allocations, or inefficient memory use. It captures a snapshot of memory allocations, showing which functions allocate memory and how much.
Example:
package main
import ( "log" "os" "runtime/pprof")
func main() { f, err := os.Create("mem.prof") if err != nil { log.Fatal(err) } defer f.Close()
// Simulate memory usage var s []string for i := 0; i < 100000; i++ { s = append(s, "data") }
if err := pprof.WriteHeapProfile(f); err != nil { log.Fatal(err) }}
Steps:
- Create an output file (e.g.,
mem.prof
). - Call
pprof.WriteHeapProfile(f)
to capture the heap profile. - Run:
go run main.go
. - Analyze:
go tool pprof mem.prof
orgo tool pprof -http=:8080 mem.prof
for a web UI.
Key points:
- Profiles memory allocations, not garbage collection.
- Use
top
,list
, orweb
inpprof
to identify allocation sources. - Thread-safe, low overhead.
- Useful for detecting leaks or optimizing memory-intensive code.
Memory profiling helps optimize resource usage in Go applications.
How do you use trace?h2
In Go, the runtime/trace
package is used to generate execution traces for debugging and performance analysis, capturing detailed events like goroutine scheduling, system calls, and garbage collection. These traces help diagnose concurrency issues or bottlenecks and are analyzed using the go tool trace
command.
Example:
package main
import ( "log" "os" "runtime/trace")
func main() { f, err := os.Create("trace.out") if err != nil { log.Fatal(err) } defer f.Close()
if err := trace.Start(f); err != nil { log.Fatal(err) } defer trace.Stop()
// Code to trace for i := 0; i < 5; i++ { go func() { _ = i * i }() } // Wait briefly to capture trace <-time.After(time.Second)}
Steps:
- Create an output file (e.g.,
trace.out
). - Start tracing with
trace.Start(f)
. - Defer
trace.Stop()
to end tracing. - Run:
go run main.go
. - Analyze:
go tool trace trace.out
(opens a web UI).
Key points:
- Captures low-level runtime events (goroutines, syscalls, GC).
- Use
go tool trace
for a visual timeline of events. - Thread-safe, but tracing adds overhead; use in development.
- Ideal for debugging concurrency, scheduling, or latency issues.
Use tracing to understand program execution and optimize performance.
What is the embed package?h2
The embed
package in Go, introduced in Go 1.16, allows embedding files (e.g., text, HTML, or binaries) into a Go program at compile time. It simplifies including static assets, like templates or configuration files, directly in the binary, eliminating external file dependencies.
Key features:
- Embedding files: Use the
//go:embed
directive to include files or directories. - Types: Embeds files as
string
,[]byte
, orembed.FS
(filesystem). - Access: Read embedded content via variables declared with
//go:embed
.
Example:
package main
import ( "embed" "fmt")
//go:embed hello.txtvar content string
func main() { fmt.Println(content) // Outputs content of hello.txt}
Key points:
//go:embed pattern
specifies files or patterns (e.g.,*.txt
).- Use
embed.FS
for multiple files or directory trees. - Thread-safe; embedded data is read-only.
- Common for web assets, templates, or static data.
- Reduces runtime file I/O, improving portability.
Use embed
for self-contained binaries with static resources.
How do you embed files?h2
In Go, files are embedded into a program using the embed
package (introduced in Go 1.16) with the //go:embed
directive. This allows including static files (e.g., text, HTML, or binaries) directly in the compiled binary, accessible as string
, []byte
, or embed.FS
.
Example (Embedding a single file):
package main
import ( "embed" "fmt")
//go:embed hello.txtvar content string
func main() { fmt.Println(content) // Outputs content of hello.txt}
Example (Embedding multiple files with embed.FS
):
package main
import ( "embed" "fmt" "io/fs")
//go:embed files/*.txtvar fsys embed.FS
func main() { data, err := fs.ReadFile(fsys, "files/doc.txt") if err != nil { fmt.Println("Error:", err) return } fmt.Println(string(data))}
Key points:
- Place
//go:embed pattern
above a variable (string
,[]byte
, orembed.FS
). - Patterns can include files or directories (e.g.,
*.txt
,dir/*
). - Use
embed.FS
withio/fs
functions to read multiple files. - Embedded data is read-only and compiled into the binary.
- Ideal for templates, static assets, or configs.
Ensure files exist at compile time; use for portable, self-contained binaries.
What is io/fs?h2
The io/fs
package in Go, introduced in Go 1.16, provides a standard interface for working with file systems in a portable, abstract way. It defines types and interfaces for reading files and directories, enabling consistent access to file systems, including in-memory or embedded ones (e.g., with embed.FS
).
Key components:
- FS interface: Defines methods like
Open(name string) (File, error)
for accessing files. - File interface: Represents a file with methods like
Read
,Stat
, andClose
. - Functions:
fs.ReadFile
,fs.ReadDir
, andfs.WalkDir
for reading files, directories, or traversing file trees. - SubFS: Creates a subset of a file system with
fs.Sub
.
Example (Using embed.FS
with io/fs
):
package main
import ( "embed" "fmt" "io/fs")
//go:embed files/*.txtvar fsys embed.FS
func main() { data, err := fs.ReadFile(fsys, "files/doc.txt") if err != nil { fmt.Println("Error:", err) return } fmt.Println(string(data))}
Key points:
- Works with any file system implementing
fs.FS
, likeos.DirFS
orembed.FS
. - Thread-safe for read-only operations.
- Used for reading embedded files, testing, or custom file systems.
- Simplifies file operations with a unified interface.
Use io/fs
for portable, flexible file system access in Go programs.
How do you read embedded files?h2
In Go, embedded files are read using the embed
package (introduced in Go 1.16) combined with the io/fs
package. The //go:embed
directive embeds files into the binary, and embed.FS
provides a file system interface to access them via fs.ReadFile
or related functions.
Example (Reading a single embedded file):
package main
import ( "embed" "fmt" "io/fs")
//go:embed hello.txtvar fsys embed.FS
func main() { data, err := fs.ReadFile(fsys, "hello.txt") if err != nil { fmt.Println("Error:", err) return } fmt.Println(string(data)) // Outputs content of hello.txt}
Example (Reading multiple embedded files):
package main
import ( "embed" "fmt" "io/fs")
//go:embed files/*.txtvar fsys embed.FS
func main() { err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if !d.IsDir() { data, _ := fs.ReadFile(fsys, path) fmt.Printf("%s: %s\n", path, data) } return nil }) if err != nil { fmt.Println("Error:", err) }}
Key points:
- Use
//go:embed pattern
to embed files or directories into anembed.FS
variable. - Read files with
fs.ReadFile(fsys, path)
for a single file’s content as[]byte
. - Use
fs.WalkDir
orfs.ReadDir
to iterate over multiple embedded files. - Check errors for missing or invalid files.
- Embedded files are read-only and bundled in the binary, ideal for templates or static assets.
This approach ensures portable, efficient access to embedded files without runtime file I/O.
What is the flag package advanced usage?h2
The flag
package in Go provides a simple way to parse command-line arguments, but its advanced usage involves creating custom flag types, organizing flags in subcommands, and handling complex parsing scenarios. Advanced features allow greater flexibility for CLI tools.
Key Advanced Features:
- Custom Flag Types: Implement the
flag.Value
interface (String()
andSet(string)
) for custom flag parsing. - Flag Sets: Use
flag.NewFlagSet
to create subcommands or separate flag groups. - Custom Parsing: Access raw arguments with
flag.Args()
or manipulate parsing behavior.
Example (Custom Flag Type):
package main
import ( "flag" "fmt" "strings")
// Custom flag type for comma-separated listtype stringSlice []string
func (s *stringSlice) String() string { return fmt.Sprintf("%v", *s)}
func (s *stringSlice) Set(value string) error { *s = strings.Split(value, ",") return nil}
func main() { var names stringSlice flag.Var(&names, "names", "Comma-separated list of names") flag.Parse() fmt.Println("Names:", names) // e.g., ./main -names=Alice,Bob}
Example (FlagSet for Subcommands):
package main
import ( "flag" "fmt" "os")
func main() { addCmd := flag.NewFlagSet("add", flag.ExitOnError) addName := addCmd.String("name", "", "Name to add")
listCmd := flag.NewFlagSet("list", flag.ExitOnError) listVerbose := listCmd.Bool("verbose", false, "Verbose output")
if len(os.Args) < 2 { fmt.Println("Expected 'add' or 'list' subcommand") os.Exit(1) }
switch os.Args[1] { case "add": addCmd.Parse(os.Args[2:]) fmt.Println("Adding:", *addName) case "list": listCmd.Parse(os.Args[2:]) fmt.Println("Listing, verbose:", *listVerbose) default: fmt.Println("Unknown subcommand") os.Exit(1) }}
Key Points:
- Custom Flags: Implement
flag.Value
for types like slices, structs, or enums. - FlagSet: Use for subcommands (e.g.,
git commit
,git push
) or isolating flag groups. - Non-Flag Args: Access with
flag.Args()
orflag.Arg(i)
for unparsed arguments. - Usage: Customize help text with
flag.Usage
orFlagSet.Usage
. - Thread-safe for parsing; suitable for complex CLI tools.
Use Cases:
- CLI tools with subcommands (like
kubectl
ordocker
). - Parsing complex inputs (e.g., JSON, lists).
- Custom validation or formatting of flag values.
For even more advanced CLI parsing, consider libraries like spf13/cobra
or urfave/cli
. Use flag
for lightweight, standard-library-based solutions.
How do you define custom flags?h2
In Go, custom flags are defined using the flag
package by implementing the flag.Value
interface, which requires two methods: String() string
(to represent the flag’s value as a string) and Set(string) error
(to parse and set the flag’s value from a string). This allows you to create flags for custom types, such as slices, structs, or enums.
Example (Custom flag for a comma-separated list):
package main
import ( "flag" "fmt" "strings")
// Custom flag typetype stringSlice []string
func (s *stringSlice) String() string { return fmt.Sprintf("%v", *s)}
func (s *stringSlice) Set(value string) error { *s = strings.Split(value, ",") return nil}
func main() { var names stringSlice flag.Var(&names, "names", "Comma-separated list of names") flag.Parse() fmt.Println("Names:", names) // e.g., ./main -names=Alice,Bob}
Steps:
- Define a type (e.g.,
stringSlice
) to hold the flag’s value. - Implement
flag.Value
by definingString()
andSet(string) error
. - Register the flag with
flag.Var(&value, "name", "usage")
. - Parse flags with
flag.Parse()
.
Key points:
String()
returns the flag’s current value as a string (used in help output).Set
parses the command-line argument and updates the flag’s value.- Use for complex types like slices, maps, or custom structs.
- Thread-safe for parsing; errors in
Set
cause parsing to fail. - Run with:
./main -names=Alice,Bob
(outputsNames: [Alice Bob]
).
Use custom flags for specialized CLI inputs, but consider libraries like spf13/cobra
for more complex CLI needs.
What is the context package for deadlines?h2
The context
package in Go is used to manage deadlines, cancellation, and request-scoped values across goroutines. For deadlines, it provides context.WithDeadline
and context.WithTimeout
to create a context that automatically cancels when a specified time is reached, ensuring operations like HTTP requests or database queries don’t run indefinitely.
Example (WithDeadline):
package main
import ( "context" "fmt" "time")
func main() { deadline := time.Now().Add(1 * time.Second) ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel()
select { case <-time.After(2 * time.Second): // Simulate work fmt.Println("Work completed") case <-ctx.Done(): fmt.Println("Deadline exceeded:", ctx.Err()) // Output: Deadline exceeded: context deadline exceeded }}
Key points:
context.WithDeadline(parent, time.Time)
: Cancels when the specified time is reached.context.WithTimeout(parent, duration)
: Cancels after a duration (wrapper forWithDeadline
).- Use
ctx.Done()
to detect cancellation in goroutines. - Defer
cancel()
to release resources early. - Ideal for time-bound operations in APIs or network calls.
Deadlines ensure timely termination of operations, preventing resource leaks.
Conclusionh2
Mastering the concepts covered in these 100 intermediate Go interview questions equips you with the skills to excel as a backend developer. From goroutines and channels for concurrency to error handling, JSON parsing, and database interactions, these topics highlight Go’s simplicity and power in building scalable, efficient applications. Understanding advanced features like the sync, net/http, and embed packages, along with profiling and debugging tools, prepares you to tackle real-world challenges. By practicing these concepts, you’ll gain confidence in writing idiomatic Go code and handling complex interview scenarios. Keep exploring Go’s standard library and community tools to deepen your expertise and stay prepared for any technical discussion.