A comprehensive list of 100 basic Go (Golang) interview questions to help you prepare for your next tech interview.

100 Basic Go (Golang) Interview Questions
81 mins

Welcome to this comprehensive guide on 100 basic Go (Golang) interview questions. Whether you’re a beginner or an experienced developer, these questions cover essential topics to help you ace your next interview.

What is Go, and why was it created?h2

What is Go?

Go, also known as Golang, is an open-source, statically typed, compiled programming language designed by Google. It emphasizes simplicity, performance, and scalability, with a focus on modern software development needs.

Why was it created?

Go was created in 2009 by Robert Griesemer, Rob Pike, and Ken Thompson to address challenges in software development at Google, including:

  1. Simplicity: To provide a language with straightforward syntax, reducing complexity compared to languages like C++.
  2. Performance: To offer near-C performance through efficient compilation and execution.
  3. Concurrency: To handle modern, multi-core systems with built-in support for goroutines and channels, simplifying concurrent programming.
  4. Scalability: To support large-scale systems with fast build times and modular design.
  5. Productivity: To streamline development with a minimal feature set, garbage collection, and a robust standard library.

Go was designed to improve developer productivity while addressing the demands of large-scale, networked, and concurrent applications, making it ideal for cloud, web, and distributed systems.

What are the key features of Go?h2

Go, or Golang, has several key features that make it popular for modern software development:

  1. Simplicity: Go has a minimalistic syntax with a small set of keywords, making it easy to learn and read.
  2. Concurrency: Built-in support for goroutines and channels enables efficient, lightweight concurrent programming for scalable applications.
  3. Fast Compilation: Go compiles quickly to machine code, improving developer productivity and iteration speed.
  4. Garbage Collection: Automatic memory management simplifies development and reduces memory-related errors.
  5. Static Typing: Strong, compile-time type checking ensures robust and reliable code.
  6. Standard Library: A comprehensive standard library supports common tasks like networking, file handling, and text processing.
  7. Cross-Platform: Go supports multiple platforms with consistent behavior, enabling easy deployment.
  8. Tooling: Built-in tools for formatting, testing, and documentation streamline development workflows.
  9. No Exceptions: Uses explicit error handling with return values, promoting clarity and reliability.
  10. Binary Output: Compiles to a single, statically linked binary, simplifying deployment without external dependencies.

These features make Go ideal for building efficient, scalable, and maintainable applications, especially in cloud and distributed systems.

How do you declare a variable in Go?h2

In Go, variables can be declared in two primary ways:

  1. Using the var Keyword:
    Explicitly declare a variable with its type, optionally initializing it.
    Example: var x int = 10 or var x int (defaults to zero value, 0 for int).

  2. Using Short Variable Declaration (:=):
    Declares and initializes a variable within a function, inferring the type from the value.
    Example: x := 10 (automatically typed as int).

Key Points:

  • var can be used at package or function level; := is only for function scope.
  • Variables declared without initialization get the type’s zero value (e.g., 0 for numbers, "" for strings, nil for pointers).
  • Multiple variables can be declared together: var a, b int = 5, 10 or a, b := 5, 10.

This approach ensures type safety and clarity, aligning with Go’s simplicity.

What is the difference between var and := for variable declaration?h2

In Go, var and := are used for variable declaration but differ in syntax, scope, and usage:

  1. Syntax:

    • var: Explicitly declares a variable with its type, e.g., var x int = 10 or var x int (zero value).
    • :=: Short-hand declaration and initialization, infers type from the value, e.g., x := 10 (inferred as int).
  2. Scope:

    • var: Can be used at package level (global) or function level.
    • :=: Only allowed within functions (local scope).
  3. Initialization:

    • var: Initialization is optional; uninitialized variables get the type’s zero value (e.g., 0, "", nil).
    • :=: Requires immediate initialization; type is inferred from the assigned value.
  4. Reusability:

    • var: Can redeclare variables in the same scope (e.g., in different blocks).
    • :=: Cannot redeclare an existing variable in the same scope but can reassign if already declared.

Example:

Go
var x int = 10 // Explicit, package or function scope
y := 20 // Short-hand, function scope only

Use var for explicit control or package-level variables; use := for concise, function-local declarations.

What are the basic data types in Go?h2

Go is a statically typed language with a concise set of basic data types. The basic data types in Go are:

  1. Numeric Types:

    • Integers:
      • Signed: int, int8, int16, int32, int64 (e.g., int is platform-dependent, typically 32 or 64 bits).
      • Unsigned: uint, uint8, uint16, uint32, uint64, byte (alias for uint8), uintptr (for pointer arithmetic).
    • Floating-Point:
      • float32, float64 (e.g., float32 for single-precision, float64 for double-precision).
    • Complex Numbers:
      • complex64, complex128 (for complex numbers with float32 or float64 components).
  2. Boolean Type:

    • bool: Represents true or false.
  3. String Type:

    • string: Represents a sequence of UTF-8 encoded characters.
  4. Rune:

    • rune: Alias for int32, used to represent a single Unicode code point.

Key Notes:

  • Go’s types are explicitly declared or inferred (e.g., via :=).
  • Zero values: 0 for numeric types, false for bool, "" for string.
  • Go does not support implicit type conversions; explicit conversion is required (e.g., int(float64Var)).

These types form the foundation for Go’s data structures like arrays, slices, and structs.

How do you create a constant in Go?h2

In Go, constants are declared using the const keyword and must be assigned a value at declaration time. Constants can only be of basic data types (bool, numeric, string) and cannot be modified after declaration.

Syntax:

Go
const identifier type = value
  • type is optional; Go infers it from the value.
  • The value must be a constant expression (evaluated at compile time).

Examples:

Go
// Single constant
const Pi float64 = 3.14159
const Name = "Golang" // Type inferred as string
// Multiple constants in a block
const (
MaxRetries int = 3
Timeout = 30 // Type inferred as int
)

Using iota for Enumerated Constants: The iota keyword is used to create a sequence of constants, incrementing by 1 starting from 0 within a const block.

Go
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
)

Key Points:

  • Constants are immutable and evaluated at compile time.
  • They can be untyped (e.g., const x = 5), allowing flexibility in type usage.
  • Constants cannot be assigned values from runtime computations (e.g., function calls).

This approach ensures constants are efficient and type-safe in Go.

What is a pointer in Go, and how do you declare one?h2

What is a Pointer in Go?
A pointer in Go is a variable that stores the memory address of another variable. Pointers allow direct manipulation of a variable’s value by referencing its memory location, which is useful for efficiency (e.g., passing large data structures) or modifying variables in functions.

How to Declare a Pointer?

  • Use the * operator before a type to declare a pointer.
  • Use the & operator to get the address of a variable.
  • The new function can also allocate memory for a pointer.

Syntax and Examples:

Go
// Declare a pointer
var p *int // Pointer to an int, initialized to nil
// Example with a variable
x := 10
p = &x // p now holds the address of x
// Using new
q := new(int) // Allocates memory for an int, returns *int
*q = 20 // Assign value via dereferencing
// Dereferencing to access value
fmt.Println(*p) // Prints 10 (value at p's address)

Key Points:

  • Dereferencing (*p) accesses the value at the pointer’s address.
  • The zero value of a pointer is nil.
  • Go does not support pointer arithmetic (unlike C), ensuring safety.
  • Pointers are commonly used in functions to modify variables or avoid copying large data.

This approach makes pointers in Go simple and safe while retaining their utility.

Explain the fmt package in Go.h2

The fmt package in Go provides functions for formatting and printing output, as well as reading input. It’s part of the standard library and widely used for I/O operations.

Key Features:

  1. Printing Output:

    • fmt.Print: Outputs text without a newline.
    • fmt.Println: Outputs text with a newline.
    • fmt.Printf: Formats output using verbs (e.g., %v for default value, %d for integers, %s for strings). Example: fmt.Printf("Name: %s, Age: %d", "Alice", 25)
  2. Reading Input:

    • fmt.Scan: Reads input from stdin into variables.
    • fmt.Scanf: Reads formatted input (e.g., fmt.Scanf("%s %d", &name, &age)).
    • fmt.Scanln: Reads input until a newline.
  3. String Formatting:

    • fmt.Sprintf: Returns a formatted string instead of printing it. Example: str := fmt.Sprintf("Score: %d", 100)

Key Points:

  • The package is simple, versatile, and supports various format verbs for precise output control.
  • It’s commonly used for debugging, logging, and user interaction.
  • For performance-critical applications, consider alternatives like strings.Builder for string concatenation.

The fmt package is essential for basic I/O and formatting in Go programs.

How do you handle errors in Go?h2

In Go, errors are handled explicitly using the error interface, which has an Error() method returning a string. Unlike exceptions in other languages, Go uses return values to manage errors, promoting clarity and control.

Error Handling Approach:

  • Functions that may fail return an error as part of their return values.
  • Check the error explicitly using if statements.

Example:

Go
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
}

Key Points:

  • Use the errors package (e.g., errors.New) or fmt.Errorf to create errors.
  • Check err != nil after calling functions that return errors.
  • Multiple return values allow returning both a result and an error.
  • Go encourages handling errors immediately, avoiding try-catch complexity.
  • Custom errors can be created by implementing the error interface.

This explicit approach ensures errors are handled predictably and transparently.

What is a slice in Go?h2

A slice in Go is a dynamically-sized, flexible view into an underlying array. It provides a way to work with sequences of elements without fixed lengths, unlike arrays.

Key Characteristics:

  • A slice is defined by a pointer to an array, a length (number of elements), and a capacity (total space in the underlying array).
  • Syntax: []type, e.g., []int for a slice of integers.
  • Slices are reference types, meaning changes to a slice affect the underlying array.

Example:

Go
// Declare and initialize a slice
s := []int{1, 2, 3} // Slice with length 3, capacity 3
// Create a slice from an array
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // Slice from index 1 to 3 (length 3, capacity 4)
// Append to a slice
s = append(s, 4) // Adds 4 to the slice

Key Points:

  • Use make([]type, len, cap) to create a slice with specific length and capacity.
  • Slices are resizable using append, which may reallocate the underlying array if capacity is exceeded.
  • Zero value of a slice is nil (length and capacity 0).
  • Slices are commonly used for dynamic lists and passing data efficiently.

Slices make Go’s data handling flexible and efficient.

How do you create an array in Go?h2

In Go, an array is a fixed-length sequence of elements of a specific type. Arrays are declared with a defined size and type, and their length is part of their type, making them immutable in size.

Syntax:

Go
var arrayName [size]type
  • size: The fixed number of elements.
  • type: The data type of elements (e.g., int, string).

Examples:

Go
// Declare an array
var numbers [5]int // Array of 5 integers, initialized to zero values (0)
// Initialize with values
numbers2 := [3]int{1, 2, 3} // Short-hand declaration
// Partial initialization
numbers3 := [5]int{1, 2} // First two elements set, others are 0
// Access and modify
numbers[0] = 10 // Set first element
fmt.Println(numbers[0]) // Access first element

Key Points:

  • Arrays have a fixed length, set at declaration, and cannot be resized.
  • The zero value of an array is an array with all elements set to their type’s zero value (e.g., 0 for int, "" for string).
  • Arrays are value types; copying an array creates a new copy.
  • Arrays are less common than slices in Go due to their fixed size, but they’re useful when the length is known and fixed.

This approach ensures arrays are simple and predictable in Go.

What is the difference between arrays and slices?h2

In Go, arrays and slices are both used to store sequences of elements, but they differ significantly in their characteristics:

1. Size:

  • Array: Fixed length, defined at declaration (e.g., [5]int). The size is part of the type.
  • Slice: Dynamic length, can grow or shrink using functions like append. Defined as []type.

2. Declaration:

  • Array: var arr [3]int or arr := [3]int{1, 2, 3}.
  • Slice: var s []int or s := []int{1, 2, 3} or s := make([]int, 3).

3. Type:

  • Array: Value type; copying creates a new copy of the entire array.
  • Slice: Reference type; points to an underlying array, so changes affect the same data.

4. Flexibility:

  • Array: Immutable size; cannot be resized.
  • Slice: Resizable using append, which may reallocate the underlying array.

5. Zero Value:

  • Array: Fully initialized with zero values (e.g., [0, 0, 0] for [3]int).
  • Slice: nil (no underlying array, length, or capacity).

6. Usage:

  • Array: Used when size is fixed and known (e.g., specific data structures).
  • Slice: Preferred for dynamic lists and passing data, as they’re more flexible.

Example:

Go
arr := [3]int{1, 2, 3} // Array
s := []int{1, 2, 3} // Slice
s = append(s, 4) // Slice grows, array cannot

Use arrays for fixed-size needs, slices for dynamic data.

How do you iterate over a slice using a for loop?h2

In Go, you can iterate over a slice using a for loop in two primary ways: using an index or using the range keyword.

1. Using Index (Traditional For Loop): Iterate by accessing elements via their indices, from 0 to the slice’s length (len(slice)).

Go
s := []int{1, 2, 3}
for i := 0; i < len(s); i++ {
fmt.Println(s[i]) // Access element at index i
}

2. Using range Keyword: The range keyword provides both the index and value of each element, or just the value if the index is ignored.

Go
s := []int{1, 2, 3}
// Index and value
for i, v := range s {
fmt.Printf("Index: %d, Value: %d\n", i, v)
}
// Ignore index
for _, v := range s {
fmt.Println(v)
}

Key Points:

  • The index-based loop is explicit but requires manual index management.
  • range is more idiomatic, concise, and less error-prone.
  • Use _ to ignore unused variables (e.g., index or value).
  • Slices are zero-based, and len(slice) gives the number of elements.

Both methods are effective, but range is preferred for its clarity and safety.

What is a map in Go?h2

A map in Go is a built-in data structure that stores key-value pairs, allowing efficient lookup, insertion, and deletion of values based on unique keys. It’s similar to dictionaries or hash tables in other languages.

Key Characteristics:

  • Maps are unordered collections of key-value pairs.
  • Keys must be of a comparable type (e.g., int, string, not slices or maps).
  • Values can be of any type.
  • Maps are reference types, so changes affect the same underlying data.

Syntax:

Go
var m map[keyType]valueType

Example:

Go
// Declare and initialize a map
m := make(map[string]int) // Create an empty map
m["apple"] = 5 // Add key-value pair
m["banana"] = 10
// Shorthand initialization
m2 := map[string]int{
"apple": 5,
"banana": 10,
}
// Access value
fmt.Println(m["apple"]) // Prints 5

Key Points:

  • Maps must be initialized with make or a literal before use; otherwise, they’re nil and cannot be assigned to.
  • Use value, ok := m[key] to check if a key exists (ok is true if found).
  • Delete entries with delete(m, key).
  • Maps are not safe for concurrent access; use synchronization (e.g., sync.Mutex) for concurrent use.

Maps are ideal for dynamic, key-based data storage in Go.

How do you declare and initialize a map?h2

In Go, a map is declared and initialized in specific ways to ensure it’s ready for use, as an uninitialized map is nil and cannot store key-value pairs.

Declaration:

  • Syntax: var mapName map[keyType]valueType
  • This creates a nil map, which cannot be used until initialized.

Initialization: There are two primary ways to initialize a map:

  1. Using make:

    • Creates an empty map with a specified key and value type.
    • Syntax: mapName := make(map[keyType]valueType)
    • Example:
      Go
      m := make(map[string]int)
      m["apple"] = 5
      fmt.Println(m) // map[apple:5]
  2. Using Map Literal:

    • Initializes a map with predefined key-value pairs.
    • Syntax: mapName := map[keyType]valueType{key1: value1, key2: value2}
    • Example:
      Go
      m := map[string]int{
      "apple": 5,
      "banana": 10,
      }
      fmt.Println(m) // map[apple:5 banana:10]

Key Points:

  • Key types must be comparable (e.g., string, int, not slices or maps).
  • Maps must be initialized before use (via make or literal); otherwise, assigning to a nil map causes a runtime panic.
  • You can specify initial capacity with make(map[keyType]valueType, capacity) for performance optimization.
  • Maps are reference types, so modifications affect the same underlying data.

This approach ensures maps are properly set up for storing and retrieving key-value pairs.

What is the zero value in Go?h2

In Go, the zero value is the default value assigned to a variable when it is declared without an explicit initial value. Each type in Go has a specific zero value, ensuring variables are always in a usable state.

Zero Values by Type:

  • Numeric Types (int, float32, float64, etc.): 0
  • Boolean (bool): false
  • String (string): "" (empty string)
  • Pointer (*T), Slice, Map, Channel, Function: nil
  • Array ([n]T): An array with all elements set to their type’s zero value
  • Struct: A struct with all fields set to their respective zero values

Example:

Go
var i int // 0
var s string // ""
var p *int // nil
var m map[string]int // nil
var arr [3]int // [0 0 0]
type Person struct {
Name string
Age int
}
var person Person // Person{Name: "", Age: 0}

Key Points:

  • Zero values eliminate the need for manual initialization, preventing uninitialized variable errors.
  • For composite types like structs, each field gets its type’s zero value.
  • A nil map or slice cannot be used until initialized with make or a literal.

This design promotes safety and predictability in Go programs.

Explain the if statement in Go.h2

In Go, the if statement is used for conditional execution of code blocks, allowing you to execute code based on whether a condition evaluates to true or false. It is simple and straightforward, with optional else and else if clauses.

Syntax:

Go
if condition {
// Code to execute if condition is true
} else if anotherCondition {
// Code to execute if anotherCondition is true
} else {
// Code to execute if all conditions are false
}

Key Features:

  1. Simple Condition:

    • The condition must evaluate to a bool (true or false).
    • Parentheses around the condition are optional but typically omitted for clarity.
    • Example:
      Go
      x := 10
      if x > 5 {
      fmt.Println("x is greater than 5")
      }
  2. Initialization Statement:

    • You can include a short variable declaration or initialization before the condition, scoped to the if block.
    • Syntax: if init; condition { ... }
    • Example:
      Go
      if num := 10; num > 5 {
      fmt.Println("num is", num)
      } // num is out of scope here
  3. Else and Else If:

    • Use else for alternative execution when the if condition is false.
    • Use else if to check additional conditions.
    • Example:
      Go
      x := 3
      if x > 5 {
      fmt.Println("x is greater than 5")
      } else if x == 3 {
      fmt.Println("x is 3")
      } else {
      fmt.Println("x is less than or equal to 5, but not 3")
      }

Key Points:

  • No parentheses are required around conditions, unlike some other languages.
  • Braces {} are mandatory, even for single-line blocks.
  • Conditions must be boolean expressions; Go does not support truthy/falsy values (e.g., 0 or nil cannot be used as conditions).
  • Variables declared in the initialization statement are scoped only to the if, else if, and else blocks.
  • Go’s explicit error handling often uses if to check err != nil.

The if statement in Go is designed for simplicity and clarity, aligning with the language’s philosophy of explicitness.

What is a switch statement, and how does it work in Go?h2

A switch statement in Go is a control structure that simplifies multiple conditional checks by comparing a value against multiple cases. It’s a cleaner alternative to long if-else chains.

How It Works:

  • A switch evaluates an expression (optional) and matches it against case clauses.
  • Each case specifies a value or condition to match.
  • When a match is found, the corresponding block executes, and the switch exits (no implicit fall-through, unlike C).
  • A default case (optional) runs if no cases match.

Syntax:

Go
switch expression {
case value1:
// Code
case value2:
// Code
default:
// Code
}

Examples:

  1. Basic Switch:

    Go
    day := "Monday"
    switch day {
    case "Monday":
    fmt.Println("Start of the week")
    case "Friday":
    fmt.Println("End of the week")
    default:
    fmt.Println("Midweek")
    }
  2. Expressionless Switch:

    • Acts like if-else with conditions in cases.
    Go
    x := 10
    switch {
    case x > 5:
    fmt.Println("x is greater than 5")
    case x == 5:
    fmt.Println("x is 5")
    default:
    fmt.Println("x is less than 5")
    }

Key Points:

  • No break needed; Go automatically exits after a case (use fallthrough for explicit fall-through).
  • Cases can include multiple values: case val1, val2:
  • The expression is optional; omitting it makes the switch condition-based.
  • Variables declared in the switch (e.g., switch x := 10; { ... }) are scoped to the switch block.

Go’s switch is concise, explicit, and avoids common pitfalls like unintended fall-through.

How do you define a function in Go?h2

In Go, a function is defined using the func keyword, followed by the function name, parameters, return type(s), and a body enclosed in braces. Functions are fundamental for structuring code and performing tasks.

Syntax:

Go
func functionName(parameterName parameterType) returnType {
// Function body
return value // If return type is specified
}

Key Components:

  • Function Name: Follows Go’s naming conventions (e.g., exported functions start with uppercase).
  • Parameters: Optional; specify name and type (e.g., x int). Multiple parameters are separated by commas.
  • Return Type: Optional; can be a single type, multiple types (using parentheses), or omitted for no return.
  • Body: Contains the logic, enclosed in {}.

Examples:

  1. Simple Function:

    Go
    func add(a int, b int) int {
    return a + b
    }
  2. Multiple Return Values:

    Go
    func swap(x, y string) (string, string) {
    return y, x
    }
  3. No Parameters or Return:

    Go
    func greet() {
    fmt.Println("Hello!")
    }

Calling a Function:

Go
result := add(3, 4) // Returns 7
a, b := swap("hello", "world") // Returns "world", "hello"
greet() // Prints "Hello!"

Key Points:

  • Use return to exit and return values; multiple returns require all values to be specified.
  • Parameters are passed by value (copies are made), but pointers can be used for pass-by-reference behavior.
  • Named return values can be declared: func example() (result int) { result = 5; return }.
  • Functions are first-class citizens and can be assigned to variables or passed as arguments.

This structure keeps Go functions clear and flexible for various use cases.

What are multiple return values in functions?h2

In Go, multiple return values allow a function to return more than one value, which is a powerful feature for handling results and errors or returning multiple related pieces of data. This is commonly used for explicit error handling and returning additional information.

Syntax:

  • Return types are listed in parentheses after the function parameters: func name(params) (type1, type2, ...).
  • The return statement specifies values for each return type.

Example:

Go
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result) // Prints: Result: 5
}

Key Points:

  • Multiple Returns: Functions can return any number of values, e.g., (int, string, error).
  • Assignment: Callers use multiple variables to capture return values: x, y := function().
  • Error Handling: A common pattern is returning a result and an error (e.g., (value, error)).
  • Named Return Values: You can name return values for clarity, automatically initialized to zero values:
    Go
    func example() (x int, err error) {
    x = 10
    return // Implicitly returns x, err
    }
  • Ignoring Returns: Use _ to ignore unwanted return values: _, err := function().

This feature makes Go’s error handling explicit and enables flexible data return, aligning with its focus on simplicity and clarity.

Explain defer in Go.h2

In Go, the defer statement schedules a function call to be executed after the surrounding function returns. It’s primarily used for cleanup tasks, such as releasing resources (e.g., closing files or unlocking mutexes), ensuring they run even if the function panics or returns early.

How It Works:

  • defer is attached to a function call, which is executed in LIFO (last-in, first-out) order if multiple defer statements exist.
  • Arguments to the deferred function are evaluated immediately (at defer time), but the call itself is postponed until the function exits.

Syntax and Example:

Go
func main() {
file, err := os.Open("example.txt")
if err != nil {
return
}
defer file.Close() // Scheduled to close when main returns
// Use the file...
data, _ := ioutil.ReadAll(file)
fmt.Println(string(data))
} // file.Close() executes here

Multiple Defers Example:

Go
func example() {
defer fmt.Println("First defer") // Executes second
defer fmt.Println("Second defer") // Executes first (LIFO)
fmt.Println("Function body")
}
// Output:
// Function body
// Second defer
// First defer

Key Points:

  • defer does not block execution; it queues the call.
  • Useful for guaranteed cleanup in functions with early returns or panics.
  • Deferred functions can access named return values.
  • Avoid overusing defer for performance-critical code, as it adds slight overhead.

This mechanism promotes reliable resource management and code clarity in Go.

What is a struct in Go?h2

A struct in Go is a composite data type that groups together zero or more fields, each with its own name and type, to represent a single cohesive entity. It’s similar to a class in other languages but simpler, as it only holds data and does not support inheritance or built-in methods (though methods can be attached separately).

Key Characteristics:

  • Fields can be of any type (e.g., int, string, other structs, pointers).
  • Structs are value types, meaning copies are made when assigned or passed to functions.
  • Structs are used to model real-world entities or data structures.

Syntax:

Go
type StructName struct {
field1 type1
field2 type2
// ...
}

Example:

Go
// Define a struct
type Person struct {
Name string
Age int
}
// Create and initialize a struct
func main() {
// Method 1: Direct initialization
p1 := Person{Name: "Alice", Age: 30}
// Method 2: Partial initialization (Age gets zero value: 0)
p2 := Person{Name: "Bob"}
// Method 3: Using pointers
p3 := &Person{"Charlie", 25}
fmt.Println(p1) // Person{Name:"Alice", Age:30}
fmt.Println(p2) // Person{Name:"Bob", Age:0}
fmt.Println(p3.Name) // Charlie
}

Key Points:

  • Zero Value: Uninitialized fields take their type’s zero value (e.g., 0 for int, "" for string).
  • Accessing Fields: Use dot notation (e.g., p1.Name).
  • Embedded Fields: Structs can include anonymous fields for composition (e.g., type B struct { A }).
  • Tags: Fields can have metadata tags for serialization (e.g., json:"name").
  • Comparison: Structs are comparable only if all fields are comparable.

Structs in Go provide a simple, flexible way to organize related data, aligning with Go’s emphasis on clarity and efficiency.

How do you create an instance of a struct?h2

In Go, creating an instance of a struct involves defining the struct and then initializing it with values. Here are the primary ways to create a struct instance:

1. Using a Struct Literal:

  • Directly initialize a struct by specifying field values in curly braces {}.
  • You can provide all fields, some fields (others get zero values), or none.
Go
type Person struct {
Name string
Age int
}
// Full initialization
p1 := Person{Name: "Alice", Age: 30}
// Partial initialization (Age gets zero value: 0)
p2 := Person{Name: "Bob"}
// No initialization (all fields get zero values)
p3 := Person{} // Person{Name: "", Age: 0}

2. Using the new Function:

  • Allocates memory for a struct and returns a pointer to it, initialized with zero values.
Go
p4 := new(Person) // Returns *Person with Name: "", Age: 0
p4.Name = "Charlie"
p4.Age = 25

3. Using a Pointer with a Struct Literal:

  • Create a pointer to a struct directly using & with a struct literal.
Go
p5 := &Person{Name: "David", Age: 40} // Returns *Person

4. Declaring a Variable and Assigning Later:

  • Declare a struct variable, which is initialized with zero values, then assign fields.
Go
var p6 Person
p6.Name = "Eve"
p6.Age = 22

Key Points:

  • Zero Values: Unspecified fields are set to their type’s zero value (e.g., 0 for int, "" for string).
  • Field Order: When using a struct literal without field names (e.g., Person{"Alice", 30}), values must match the field order in the struct definition, but naming fields is preferred for clarity.
  • Pointers: Using new or & creates a pointer (*Person), useful for passing structs efficiently or modifying them in functions.
  • Accessing Fields: Use dot notation (e.g., p1.Name) for both struct and pointer instances (Go automatically dereferences pointers).

These methods provide flexibility for creating and initializing struct instances based on your needs.

What are methods in Go?h2

In Go, a method is a function with a special receiver argument, allowing it to operate on a specific type, such as a struct or custom type. Methods enable object-oriented behavior by associating functions with data, similar to methods in other languages, but Go avoids traditional class-based inheritance.

Key Characteristics:

  • The receiver is specified in parentheses before the method name, with a name and type (e.g., (p Person)).
  • Methods can be defined on any named type, not just structs, except for built-in types like int.
  • Receivers can be value receivers (func (t Type)) or pointer receivers (func (t *Type)).

Syntax:

Go
func (receiverName ReceiverType) MethodName(params) returnType {
// Method body
}

Example:

Go
type Person struct {
Name string
Age int
}
// Value receiver
func (p Person) Greet() string {
return "Hello, I'm " + p.Name
}
// Pointer receiver (can modify the struct)
func (p *Person) HaveBirthday() {
p.Age++
}
func main() {
p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Greet()) // Hello, I'm Alice
p.HaveBirthday()
fmt.Println(p.Age) // 31
}

Key Points:

  • Value vs. Pointer Receivers:
    • Value receiver: Operates on a copy, so changes don’t affect the original.
    • Pointer receiver: Operates on the original data, allowing modifications.
  • Method Calls: Go automatically handles pointer dereferencing, so p.Greet() works for both Person and *Person.
  • No Inheritance: Go uses composition (embedding) instead of inheritance for code reuse.
  • Methods promote encapsulation and modularity, aligning with Go’s simplicity.

This approach makes methods flexible and explicit for associating behavior with types.

How do you attach a method to a struct?h2

In Go, to attach a method to a struct, you define a function with a receiver that specifies the struct type. The receiver is declared in parentheses before the function name, associating the method with the struct.

Syntax:

Go
func (receiverName ReceiverType) MethodName(params) returnType {
// Method body
}
  • receiverName: A name for the receiver (e.g., p for a struct instance).
  • ReceiverType: The struct type (or *StructType for a pointer receiver).
  • MethodName: The name of the method.
  • params and returnType: Optional parameters and return values.

Example:

Go
type Person struct {
Name string
Age int
}
// Value receiver (operates on a copy)
func (p Person) Greet() string {
return "Hello, I'm " + p.Name
}
// Pointer receiver (can modify the struct)
func (p *Person) HaveBirthday() {
p.Age++
}
func main() {
p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Greet()) // Hello, I'm Alice
p.HaveBirthday()
fmt.Println(p.Age) // 31
}

Key Points:

  • Value Receiver: func (p Person) works on a copy, suitable for read-only operations.
  • Pointer Receiver: func (p *Person) allows modification of the struct, useful for mutating state.
  • Go automatically dereferences pointers, so p.Greet() works for both Person and *Person.
  • Methods can only be defined on types declared in the same package.
  • Use pointer receivers when modifying the struct or to avoid copying large structs.

This approach ties behavior to structs, enabling object-oriented programming in Go’s simple, explicit style.

What is the main function in Go?h2

The main function in Go is the entry point of a program. It’s a special function that the Go runtime calls when a program starts execution. Every executable Go program must have a main function in a package named main.

Key Characteristics:

  • Signature: Defined as func main() {} with no parameters and no return value.
  • Package: Must reside in the main package (package main).
  • Purpose: Initializes the program and contains the primary logic or calls other functions to start execution.

Example:

Go
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!") // Program starts here
}
  • Output: Hello, Go!

Key Points:

  • The main function is mandatory for standalone executable programs but not for libraries.
  • It cannot take arguments or return values, unlike some languages (use os.Args for command-line arguments).
  • The program exits when main returns, or explicitly via os.Exit().
  • Multiple main functions can exist in separate programs but not within the same package.

The main function provides a clear, standardized starting point for Go programs, aligning with Go’s simplicity.

How do you import packages in Go?h2

In Go, packages are imported using the import keyword, which allows you to access functions, types, and variables from other packages. Imports are declared at the top of a Go file, typically after the package declaration.

Syntax:

Go
import (
"package1"
"package2"
)

Examples:

  1. Single Import:

    Go
    import "fmt"
  2. Multiple Imports:

    Go
    import (
    "fmt"
    "os"
    "math/rand"
    )
  3. Aliasing a Package:

    • Use an alias to rename a package for clarity or to avoid conflicts.
    Go
    import (
    f "fmt" // Alias fmt as f
    )
    f.Println("Hello") // Use alias
  4. Importing Custom Packages:

    • Import packages from your project or external modules using their path.
    Go
    import "github.com/user/project/mypackage"

Key Points:

  • Standard library packages (e.g., fmt, os, math) don’t require external installation.
  • External packages need go get to download (e.g., go get github.com/user/project).
  • Use parentheses for multiple imports for readability, though a single import "pkg" is valid.
  • Dot Import (e.g., import . "fmt"): Allows using package contents without prefix (not recommended).
  • Blank Import (e.g., import _ "pkg"): Imports for side effects (e.g., initialization).
  • Unused imports cause compile errors; remove them or use go mod tidy.

This approach ensures modular, reusable code in Go programs.

What is the GOPATH environment variable?h2

The GOPATH environment variable in Go specifies the workspace directory where Go stores source code, dependencies, and compiled binaries. It’s a key part of Go’s project structure, especially in older versions (pre-Go modules, introduced in Go 1.11).

Purpose:

  • Source Code: Stores your Go source files in $GOPATH/src.
  • Dependencies: Stores imported packages in $GOPATH/pkg.
  • Binaries: Stores compiled executables in $GOPATH/bin.

Default Value:

  • If unset, defaults to ~/go on Unix-like systems or %USERPROFILE%\go on Windows.

Example Setup:

  1. Set GOPATH:
    Terminal window
    export GOPATH=$HOME/go
  2. Directory structure:
    $GOPATH/
    src/ # Your code and imported packages (e.g., github.com/user/project)
    pkg/ # Compiled package archives
    bin/ # Compiled executables

Usage Example:

  • Place your project in $GOPATH/src/myproject.
  • Run go build to compile, and the binary goes to $GOPATH/bin.

Key Points:

  • Since Go 1.11, Go modules (go.mod) are preferred, reducing reliance on GOPATH for dependency management.
  • With modules, projects can live anywhere, and GOPATH is less critical but still used for tools and binaries.
  • Check GOPATH with go env GOPATH.
  • Multiple paths can be set (e.g., GOPATH=/path1:/path2), though rarely used.

GOPATH organizes Go projects, but modules have largely replaced it for modern development.

Explain the Go runtime.h2

The Go runtime is a lightweight, built-in system that manages the execution of Go programs, acting like a virtual machine or mini-OS. It’s implemented in the runtime package and handles low-level operations transparently.

Key Components:

  • Goroutine Scheduler: Manages lightweight threads (goroutines) on OS threads using an M model, enabling efficient concurrency.
  • Garbage Collector (GC): Automatic memory management with concurrent, low-latency collection to reclaim unused memory.
  • Memory Allocator: Efficient heap allocation for variables, avoiding manual memory management.
  • Concurrency Primitives: Supports channels, mutexes, and waitgroups for safe parallel execution.
  • Other Features: Includes stack management, signal handling, and profiling tools.

You can interact with it via functions like runtime.GOMAXPROCS to set processor count or runtime.NumGoroutine to monitor. It’s designed for performance in networked and concurrent applications, like servers.

What is a rune in Go?h2

In Go, a rune is an alias for the int32 type and represents a single Unicode code point. It is used to handle individual characters in strings, which in Go are UTF-8 encoded.

Key Characteristics:

  • A rune is a 32-bit integer that can represent any Unicode character (from U+0000 to U+10FFFF).
  • Strings in Go are sequences of bytes, but iterating over a string yields runes to handle characters correctly.
  • Used when working with individual characters or Unicode text processing.

Example:

Go
s := "Hello, 世界" // String with ASCII and Unicode characters
for i, r := range s {
fmt.Printf("Index: %d, Rune: %c (Code point: %U)\n", i, r, r)
}
// Output:
// Index: 0, Rune: H (Code point: U+0048)
// Index: 1, Rune: e (Code point: U+0065)
// ...
// Index: 7, Rune: 世 (Code point: U+4E16)
// Index: 10, Rune: 界 (Code point: U+754C)

Key Points:

  • A rune literal is written with single quotes, e.g., r := 'A' (Unicode U+0041).
  • Use []rune(s) to convert a string to a slice of runes for character-level processing.
  • UTF-8 encoding means a single rune may correspond to multiple bytes in a string (e.g., is 3 bytes).
  • The unicode package provides utilities for rune manipulation (e.g., unicode.IsLetter).

Runes ensure proper handling of Unicode characters, making Go suitable for internationalized applications.

How do you handle strings in Go?h2

In Go, strings are handled as immutable, UTF-8 encoded sequences of bytes. The string type is a fundamental type, and Go provides built-in features and the strings package for common string operations. Here’s how strings are managed:

Key Characteristics:

  • Immutability: Strings cannot be modified after creation; operations create new strings.
  • UTF-8 Encoding: Strings are encoded in UTF-8, supporting Unicode characters.
  • Byte Representation: A string is a sequence of bytes, accessible as []byte.

Basic Operations:

  1. Declaration and Initialization:

    Go
    var s string = "Hello, World!" // Explicit declaration
    s2 := "Go" // Short-hand
  2. Accessing Characters:

    • Use indexing to access bytes: s[0] (returns byte, e.g., 72 for 'H').
    • Use range or []rune for Unicode characters (runes):
      Go
      s := "Hello, 世界"
      for i, r := range s {
      fmt.Printf("Index: %d, Char: %c\n", i, r)
      }
      runes := []rune(s) // Convert to slice of runes
  3. Length:

    • len(s) returns the number of bytes (not characters, due to UTF-8).
    Go
    s := "世界"
    fmt.Println(len(s)) // 6 (bytes, as each character is 3 bytes)
    fmt.Println(utf8.RuneCountInString(s)) // 2 (runes)
  4. Concatenation:

    • Use + for simple concatenation (less efficient for large operations).
    • Use strings.Builder for efficient concatenation:
      Go
      var b strings.Builder
      b.WriteString("Hello")
      b.WriteString(" World")
      result := b.String() // "Hello World"

Common Operations with strings Package:

  • Splitting: strings.Split(s, delimiter) splits a string into a slice.
    Go
    s := "a,b,c"
    parts := strings.Split(s, ",") // []string{"a", "b", "c"}
  • Joining: strings.Join(slice, separator) combines strings.
    Go
    parts := []string{"a", "b", "c"}
    s := strings.Join(parts, ",") // "a,b,c"
  • Searching: strings.Contains(s, substr), strings.Index(s, substr).
    Go
    fmt.Println(strings.Contains("Hello", "ll")) // true
  • Case Conversion: strings.ToLower(s), strings.ToUpper(s).
    Go
    fmt.Println(strings.ToUpper("hello")) // "HELLO"
  • Trimming: strings.Trim(s, cutset) removes characters from both ends.
    Go
    fmt.Println(strings.Trim(" hello ", " ")) // "hello"

Key Points:

  • Strings are immutable, so operations like concatenation create new strings.
  • Use []rune or range for Unicode-safe character processing.
  • For performance, prefer strings.Builder over + for repeated concatenations.
  • The unicode/utf8 package helps with rune counting and validation.
  • Strings are comparable with ==, <, etc., based on lexicographical order.

This approach ensures strings in Go are handled efficiently and correctly, especially for Unicode text.

What is the difference between string and []byte?h2

In Go, both string and []byte represent sequences of bytes, but they differ in purpose, mutability, and usage:

1. Mutability:

  • String: Immutable; once created, its contents cannot be changed. Operations like concatenation create a new string.
  • []byte: Mutable; you can modify the bytes in a slice directly (e.g., b[0] = 65).

2. Purpose:

  • String: Designed for text, representing UTF-8 encoded character sequences. Used for human-readable data.
  • []byte: A slice of bytes, used for raw binary data or when mutability is needed (e.g., I/O operations).

3. Type and Operations:

  • String: A built-in type with string-specific operations in the strings package (e.g., strings.ToUpper).
  • []byte: A slice of byte (alias for uint8), manipulated like any slice, with operations in the bytes package (e.g., bytes.Compare).

4. Conversion:

  • Convert between them explicitly:
    Go
    s := "hello"
    b := []byte(s) // Convert string to []byte
    s2 := string(b) // Convert []byte to string
  • Conversions copy data, as strings are immutable.

5. Memory and Performance:

  • String: Immutable, often optimized for sharing (e.g., string literals share memory).
  • []byte: Mutable, may require reallocation when resized (like any slice).

Example:

Go
s := "hello"
b := []byte(s)
b[0] = 'H' // Modifies []byte to "Hello"
s2 := string(b) // New string "Hello"
// s[0] = 'H' // Error: strings are immutable

Key Points:

  • Use string for text processing and immutability.
  • Use []byte for binary data or when modification is needed (e.g., buffers).
  • Conversions are common but have a cost due to copying.

This distinction ensures clarity in handling text versus raw bytes in Go.

How do you concatenate strings efficiently?h2

In Go, strings are immutable, so concatenating with + in loops is inefficient due to repeated allocations. For efficiency, use the strings.Builder type, which minimizes memory copies by building strings in a buffer.

Example:

Go
var builder strings.Builder
builder.WriteString("Hello")
builder.WriteString(" World")
result := builder.String() // "Hello World"

Alternatively, use strings.Join for a slice of strings:

Go
words := []string{"Hello", "World"}
result := strings.Join(words, " ")

Key Points:

  • strings.Builder is best for incremental concatenation, especially in loops.
  • strings.Join is ideal for combining multiple strings with a separator.
  • bytes.Buffer works for byte operations but is less common for pure strings.
  • Use builder.Grow(n) to pre-allocate space if the size is known.

These methods ensure O(n) complexity, unlike + which is O(n²) in loops, making them performant for large concatenations.

What is the len function used for?h2

In Go, the len function returns the length of a data structure, such as a string, slice, array, map, or channel. It’s a built-in function that provides the number of elements or bytes, depending on the type.

  • String: Returns the number of bytes (not runes) in a UTF-8 encoded string.
    Go
    s := "Hello, 世界"
    fmt.Println(len(s)) // 11 (bytes)
  • Slice/Array: Returns the number of elements.
    Go
    slice := []int{1, 2, 3}
    fmt.Println(len(slice)) // 3
  • Map: Returns the number of key-value pairs.
    Go
    m := map[string]int{"a": 1, "b": 2}
    fmt.Println(len(m)) // 2
  • Channel: Returns the number of queued elements.
    Go
    ch := make(chan int, 3)
    ch <- 1
    fmt.Println(len(ch)) // 1

Key Points:

  • len is fast, with O(1) complexity.
  • For strings, use utf8.RuneCountInString for character count.
  • Works on built-in types; custom types need explicit methods.

It’s essential for iterating, validating, or sizing data structures efficiently.

How do you use the range keyword in loops?h2

In Go, the range keyword is used in for loops to iterate over elements of slices, arrays, strings, maps, or channels. It provides a concise way to access indices and values or keys and values, depending on the data type.

Syntax:

Go
for index, value := range collection {
// Use index and value
}

Examples:

  • Slice/Array: Returns index and value.
    Go
    s := []string{"a", "b", "c"}
    for i, v := range s {
    fmt.Printf("Index: %d, Value: %s\n", i, v)
    }
    // Output: Index: 0, Value: a; Index: 1, Value: b; Index: 2, Value: c
  • String: Returns index and rune (Unicode code point).
    Go
    s := "Hello"
    for i, r := range s {
    fmt.Printf("Index: %d, Rune: %c\n", i, r)
    }
  • Map: Returns key and value.
    Go
    m := map[string]int{"a": 1, "b": 2}
    for k, v := range m {
    fmt.Printf("Key: %s, Value: %d\n", k, v)
    }

Key Points:

  • Use _ to ignore index or value (e.g., for _, v := range s).
  • Map iteration order is random.
  • For channels, range reads until the channel is closed.

range simplifies iteration, making code clean and readable.

What are anonymous functions in Go?h2

In Go, anonymous functions are functions defined without a name, often used for inline or short-lived tasks. They can be assigned to variables, passed as arguments, or used in closures.

Syntax:

Go
func(params) returnType {
// Function body
}

Examples:

  • Assign to Variable:
    Go
    add := func(a, b int) int {
    return a + b
    }
    fmt.Println(add(2, 3)) // Output: 5
  • Inline Execution:
    Go
    func() {
    fmt.Println("Hello!")
    }() // Immediate call, Output: Hello!
  • As Argument:
    Go
    func apply(f func(int) int, x int) int {
    return f(x)
    }
    result := apply(func(x int) int { return x * 2 }, 5) // Output: 10

Key Points:

  • Anonymous functions can capture variables from their surrounding scope (closures).
  • Useful for callbacks, goroutines, or one-off logic.
  • Can have parameters and return values like named functions.
  • Often used with defer or in concurrent programming.

Anonymous functions provide flexibility for concise, context-specific code in Go.

How do you pass arguments to functions?h2

In Go, arguments are passed to functions by value, meaning copies of the arguments are sent to the function. You can also pass pointers to modify the original data.

Syntax:

Go
func functionName(param1 type1, param2 type2) {
// Use param1, param2
}

Examples:

  • Pass by Value:
    Go
    func add(a, b int) int {
    return a + b
    }
    result := add(2, 3) // Copies of 2 and 3 are passed, Output: 5
  • Pass by Pointer:
    Go
    func increment(x *int) {
    *x++ // Modify original value
    }
    num := 5
    increment(&num)
    fmt.Println(num) // Output: 6
  • Multiple Arguments:
    Go
    func greet(name string, age int) {
    fmt.Printf("%s is %d years old\n", name, age)
    }
    greet("Alice", 30) // Output: Alice is 30 years old

Key Points:

  • Pass by value: Changes to parameters don’t affect originals.
  • Pass by pointer: Use *type and &variable to modify originals.
  • Parameters are type-checked and must match the function signature.
  • Use variadic parameters (...type) for variable argument counts.

This approach ensures clarity and control in function calls.

What is a variadic function?h2

A variadic function in Go accepts a variable number of arguments of the same type, declared using the ... syntax before the parameter type. It’s useful for functions like fmt.Println that can take any number of inputs.

Syntax:

Go
func functionName(params ...type) {
// params is treated as a slice of type
}

Example:

Go
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
func main() {
result := sum(1, 2, 3, 4) // Pass any number of ints
fmt.Println(result) // Output: 10
result = sum() // Works with zero arguments
fmt.Println(result) // Output: 0
}

Key Points:

  • The variadic parameter (numbers ...int) is treated as a slice ([]int) inside the function.
  • Must be the last parameter in the function signature.
  • You can pass a slice using slice...:
    Go
    nums := []int{1, 2, 3}
    result := sum(nums...) // Spreads slice into arguments
  • Useful for flexible APIs and utility functions.

Variadic functions simplify handling variable argument counts in Go.

How do you declare a constant block?h2

In Go, a constant block is declared using the const keyword to group multiple constants together, often for better organization or to use iota for enumerated values. Constants are immutable and must be assigned at declaration.

Syntax:

Go
const (
identifier1 type = value1
identifier2 = value2
// ...
)

Example:

Go
const (
MaxRetries int = 3
Timeout = 30 // Type inferred as int
Debug = true
)

Using iota for Sequential Constants:

Go
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
)

Key Points:

  • Constants in a block can share a type or infer it from values.
  • iota starts at 0 and increments by 1 per line in a const block.
  • Constants can only be basic types (int, string, bool, etc.) and must be computable at compile time.
  • Grouping improves readability and maintains related constants together.

This approach ensures clean, maintainable constant declarations for Go programs.

What is the iota keyword?h2

In Go, the iota keyword is used in a const block to create a sequence of incrementing values, starting from 0. It simplifies defining enumerated constants by automatically incrementing for each constant in the block.

Syntax:

Go
const (
name1 = iota // Starts at 0
name2 // Increments to 1
name3 // Increments to 2
)

Example:

Go
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
)
fmt.Println(Sunday, Monday, Tuesday) // Output: 0 1 2

Advanced Example (with expressions):

Go
const (
KB = 1 << (10 * iota) // 1 << 0 = 1
MB // 1 << 10 = 1024
GB // 1 << 20 = 1048576
)

Key Points:

  • iota resets to 0 at the start of each const block.
  • Increments by 1 for each constant, even if unassigned.
  • Can be used in expressions (e.g., bit shifts for powers of 2).
  • Skipped lines (e.g., _ = iota) increment iota without assigning.
  • Only works in const blocks for compile-time constants.

iota simplifies creating sequential or patterned constants in Go.

Explain type conversions in Go.h2

In Go, type conversions are explicit operations to convert a value from one type to another, as Go doesn’t allow implicit conversions. Use the Type(value) syntax to perform conversions, ensuring type safety.

Syntax:

Go
convertedValue := Type(value)

Examples:

  • Numeric Conversions:

    Go
    i := 42 // int
    f := float64(i) // Convert to float64
    u := uint(i) // Convert to uint
    fmt.Println(f, u) // Output: 42 42
  • String to/from []byte:

    Go
    s := "hello"
    b := []byte(s) // Convert string to []byte
    s2 := string(b) // Convert []byte to string
  • String to Rune:

    Go
    r := rune("A"[0]) // Convert first byte to rune
    fmt.Println(r) // Output: 65 (Unicode for 'A')

Key Points:

  • Conversions are required between incompatible types (e.g., int to float64, int32 to int64).
  • No implicit conversions; int + float64 requires explicit conversion.
  • Conversions may truncate or wrap values (e.g., int32 to int8).
  • Use packages like strconv for string-to-number conversions (e.g., strconv.Atoi).

Explicit conversions ensure clarity and prevent type-related errors in Go.

What is the bool type in Go?h2

In Go, the bool type is a built-in data type that represents boolean values, which can be either true or false. It’s used for conditional logic and comparisons.

Key Characteristics:

  • Values: Only true or false (no implicit conversion from other types like 0 or nil).
  • Zero Value: false for uninitialized bool variables.
  • Usage: Common in if, for, and switch statements, or as function return values.

Example:

Go
var isActive bool = true
if isActive {
fmt.Println("System is active") // Output: System is active
}
flag := false // Short-hand declaration
fmt.Println(flag) // Output: false

Key Points:

  • Operators: Supports logical operators (&&, ||, !) and comparison operators (==, !=).
  • Size: Occupies 1 byte of memory.
  • No Implicit Conversion: Values like 0 or nil cannot be used as booleans; explicit comparison is needed (e.g., x == 0).
  • Type Safety: Must explicitly convert other types to bool in specific contexts.

The bool type ensures clear, type-safe logic in Go programs.

How do you use logical operators?h2

In Go, logical operators are used with bool values to perform logical operations in conditional expressions. They include && (AND), || (OR), and ! (NOT).

Operators:

  • && (AND): Returns true if both operands are true.
  • || (OR): Returns true if at least one operand is true.
  • ! (NOT): Returns the opposite of the operand’s boolean value.

Example:

Go
x, y := true, false
// AND
if x && !y {
fmt.Println("x is true and y is false") // Output
}
// OR
if x || y {
fmt.Println("At least one is true") // Output
}
// NOT
if !y {
fmt.Println("y is not true") // Output
}

Key Points:

  • Operands must be bool type; no implicit conversion (e.g., 0 or nil aren’t valid).
  • Short-circuit evaluation: && stops if the first operand is false; || stops if the first is true.
  • Used in if, for, or switch statements for control flow.
  • Parentheses can clarify precedence, but ! has the highest, followed by &&, then ||.

Logical operators enable concise and clear conditional logic in Go.

What are bitwise operators in Go?h2

In Go, bitwise operators perform operations on the binary representations of integers. They are used for low-level manipulation of bits in numeric types (int, uint, int32, etc.).

Bitwise Operators:

  • & (AND): Sets a bit to 1 if both corresponding bits are 1.
  • | (OR): Sets a bit to 1 if at least one corresponding bit is 1.
  • ^ (XOR): Sets a bit to 1 if exactly one corresponding bit is 1.
  • &^ (AND NOT): Clears bits (sets to 0 where the second operand has 1s).
  • << (Left Shift): Shifts bits left, filling with zeros.
  • >> (Right Shift): Shifts bits right, filling with the sign bit for signed types.

Example:

Go
a, b := 5, 3 // a: 0101, b: 0011
fmt.Println(a & b) // 1 (0001)
fmt.Println(a | b) // 7 (0111)
fmt.Println(a ^ b) // 6 (0110)
fmt.Println(a &^ b) // 4 (0100)
fmt.Println(a << 1) // 10 (1010)
fmt.Println(a >> 1) // 2 (0010)

Key Points:

  • Operands must be integers; results depend on the type (e.g., int or uint).
  • Useful for flags, bitmasks, or low-level optimizations.
  • No implicit type conversion; operands must match.

Bitwise operators enable efficient bit-level operations in Go.

How do you declare multi-dimensional arrays?h2

In Go, multi-dimensional arrays are declared by specifying multiple dimensions in the array type, with each dimension having a fixed size. Arrays in Go are fixed-length and type-safe.

Syntax:

Go
var arrayName [size1][size2]...[sizeN]type

Examples:

  • 2D Array:

    Go
    var matrix [3][3]int // 3x3 array of integers
    matrix[0][0] = 1 // Set element
    fmt.Println(matrix) // [[1 0 0] [0 0 0] [0 0 0]]
  • Initialize with Values:

    Go
    grid := [2][2]int{{1, 2}, {3, 4}} // 2x2 array
    fmt.Println(grid) // [[1 2] [3 4]]
  • 3D Array:

    Go
    var cube [2][2][2]int
    cube[0][0][0] = 5

Key Points:

  • All dimensions must have a fixed size at declaration.
  • Zero value is an array with all elements set to the type’s zero value (e.g., 0 for int).
  • Arrays are value types; copying creates a new copy.
  • For dynamic sizes, use slices (e.g., [][]int) instead of arrays.
  • Access elements with multiple indices (e.g., matrix[i][j]).

Multi-dimensional arrays are useful for fixed-size grid-like data structures in Go.

What is the default value for uninitialized variables?h2

In Go, uninitialized variables are automatically assigned their zero value, ensuring no undefined behavior. The zero value depends on the variable’s type:

  • Numeric types (int, float64, etc.): 0
  • Boolean (bool): false
  • String (string): "" (empty string)
  • Pointer, Slice, Map, Channel, Function: nil
  • Array: Array with all elements set to their type’s zero value
  • Struct: Struct with all fields set to their respective zero values

Example:

Go
var i int // 0
var s string // ""
var p *int // nil
var arr [3]int // [0 0 0]
var m map[string]int // nil
type Person struct { Name string }
var person Person // Person{Name: ""}

Key Points:

  • Zero values make variables usable without explicit initialization.
  • nil types (e.g., maps, slices) need initialization (e.g., via make) before use.
  • Struct fields inherit their type’s zero value.
  • This design ensures safety and predictability in Go programs.

Zero values eliminate uninitialized variable errors, aligning with Go’s simplicity.

Explain short variable declarations.h2

In Go, short variable declarations use the := operator to declare and initialize variables in a concise way. They are used within functions and infer the type from the assigned value.

Syntax:

Go
variable := value

Examples:

Go
x := 42 // Declares x as int
name := "Alice" // Declares name as string
a, b := 10, 20 // Multiple declarations

Key Points:

  • Only allowed inside functions, not at package level.
  • Type is inferred from the value (e.g., 42int, "text"string).
  • Must initialize the variable at declaration.
  • Can declare multiple variables: x, y := 1, "test".
  • Cannot redeclare an existing variable in the same scope with :=, but can reassign one of the variables if at least one new variable is introduced:
    Go
    x, y := 10, 20 // New variables
    x, z := 30, 40 // x reassigned, z new; valid
    // x := 50 // Error: no new variables
  • Promotes concise, readable code but requires initialization.

Short declarations streamline local variable creation, enhancing Go’s simplicity.

How do you use labels in loops?h2

In Go, labels in loops allow you to control the flow of nested loops using break or continue to target a specific loop. A label is an identifier followed by a colon, placed before a loop.

Syntax:

Go
LabelName:
for ... {
for ... {
if condition {
break LabelName // Breaks outer loop
// or continue LabelName // Continues outer loop
}
}
}

Example:

Go
OuterLoop:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break OuterLoop // Exits both loops
}
fmt.Printf("i: %d, j: %d\n", i, j)
}
}
// Output: i: 0, j: 0; i: 0, j: 1; i: 0, j: 2; i: 1, j: 0

Key Points:

  • Labels are used with break or continue to control outer loops in nested structures.
  • Must be declared immediately before a for loop.
  • Improves clarity in complex loops but should be used sparingly to avoid confusion.
  • Labels are scoped to the function they’re defined in.

Labels provide precise control over nested loop execution in Go.

What is the goto statement?h2

In Go, the goto statement transfers control to a labeled statement within the same function, allowing you to jump to a specific point in the code. It’s a low-level control flow mechanism, but its use is generally discouraged in favor of structured constructs like loops or conditionals.

Syntax:

Go
goto LabelName
// ...
LabelName:
// Code

Example:

Go
func main() {
x := 0
if x == 0 {
goto Skip
}
fmt.Println("This is skipped")
Skip:
fmt.Println("Jumped here") // Output: Jumped here
}

Key Points:

  • The label must be in the same function and defined after or at the goto statement.
  • Cannot jump over variable declarations, as this causes a compile error.
  • Useful in rare cases, like error handling in C-style code or breaking out of deeply nested loops.
  • Overuse can make code hard to read, so prefer for, if, or return for clarity.
  • Labels are case-sensitive and must follow Go identifier rules.

While goto is supported, Go’s design encourages cleaner alternatives for maintainable code.

How do you break out of nested loops?h2

In Go, to break out of nested loops, you can use a label with the break statement to exit the outer loop from within an inner loop. This avoids breaking only the innermost loop.

Syntax:

Go
OuterLoop:
for ... {
for ... {
if condition {
break OuterLoop // Exits the labeled outer loop
}
}
}

Example:

Go
OuterLoop:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break OuterLoop // Exits both loops
}
fmt.Printf("i: %d, j: %d\n", i, j)
}
}
// Output: i: 0, j: 0; i: 0, j: 1; i: 0, j: 2; i: 1, j: 0

Key Points:

  • Label must be placed before the outer for loop.
  • break without a label only exits the innermost loop.
  • Alternatively, use a return in a function or goto (rarely recommended).
  • Use sparingly to keep code clear; consider refactoring complex loops.

Labels with break provide precise control for exiting nested loops in Go.

What is a type alias in Go?h2

In Go, a type alias is a way to create an alternative name for an existing type without creating a new distinct type. It’s declared using the = operator in a type declaration, introduced in Go 1.9 for smoother code refactoring and interoperability.

Syntax:

Go
type AliasName = ExistingType

Example:

Go
type MyInt = int
func main() {
var x MyInt = 42
var y int = x // No conversion needed; MyInt is int
fmt.Println(y) // Output: 42
}

Key Points:

  • Unlike a new type (type NewType ExistingType), a type alias is fully interchangeable with the original type; no type conversion is needed.
  • Useful for renaming types during refactoring or for clarity in specific contexts.
  • Methods defined on the original type are available to the alias.
  • Example with standard library:
    Go
    type MyString = string
    s := MyString("hello")
    fmt.Println(strings.ToUpper(s)) // Works, as MyString is string
  • Cannot define new methods on a type alias, only on new types.

Type aliases enhance code readability and maintain compatibility without creating distinct types.

How do you declare embedded fields in structs?h2

In Go, embedded fields in structs allow you to include one struct type within another without a field name, enabling composition and field/method promotion. The embedded type’s fields and methods become part of the outer struct.

Syntax:

Go
type Outer struct {
EmbeddedType // Anonymous field
// Other fields
}

Example:

Go
type Address struct {
City string
Zip string
}
type Person struct {
Address // Embedded struct
Name string
}
func main() {
p := Person{
Address: Address{City: "New York", Zip: "10001"},
Name: "Alice",
}
fmt.Println(p.City) // Access embedded field directly
fmt.Println(p) // Person{Address{City:"New York", Zip:"10001"}, Name:"Alice"}
}

Key Points:

  • Embedded fields are declared by specifying the type without a name.
  • Fields and methods of the embedded type are promoted to the outer struct, accessible directly (e.g., p.City instead of p.Address.City).
  • Use the full path (p.Address.City) to resolve ambiguity if fields conflict.
  • Supports multiple embedded types, but name collisions require explicit access.
  • Embedding promotes composition over inheritance, aligning with Go’s design.

Embedded fields simplify struct composition and code reuse in Go.

What are tags in struct fields?h2

In Go, struct tags are metadata strings attached to struct fields, typically used to control how fields are handled by libraries like encoding (e.g., JSON, XML) or reflection-based tools. They are defined in backticks after the field type.

Syntax:

Go
type StructName struct {
FieldName Type `key:"value" key2:"value2"`
}

Example:

Go
type Person struct {
Name string `json:"full_name" db:"user_name"`
Age int `json:"age"`
}
func main() {
p := Person{Name: "Alice", Age: 30}
jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData)) // Output: {"full_name":"Alice","age":30}
}

Key Points:

  • Tags are key-value pairs in backticks, commonly used by packages like encoding/json or database/sql.
  • Format: key:"value" (e.g., json:"field_name" for JSON serialization).
  • Multiple tags can be specified (e.g., json:"name" db:"user").
  • Use - to omit a field (e.g., json:"-").
  • Accessed via reflection (reflect package) at runtime.
  • Widely used for serialization, validation, or ORM mapping.

Struct tags provide a flexible way to add metadata for external processing in Go.

How do you access struct fields?h2

In Go, struct fields are accessed using the dot (.) operator, referencing the field name on a struct instance or pointer. Fields can be read or modified if they are exported (start with an uppercase letter).

Syntax:

Go
structInstance.FieldName

Example:

Go
type Person struct {
Name string
Age int
}
func main() {
// Struct instance
p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Name) // Access: Alice
p.Age = 31 // Modify
fmt.Println(p.Age) // 31
// Pointer to struct
pp := &Person{Name: "Bob", Age: 25}
fmt.Println(pp.Name) // Access: Bob (automatic dereference)
pp.Age = 26 // Modify
}

Key Points:

  • Use structName.FieldName for direct instances, pointerName.FieldName for pointers (Go auto-dereferences).
  • Exported fields (uppercase) are accessible outside the package; unexported (lowercase) are not.
  • Embedded struct fields can be accessed directly (e.g., p.EmbeddedField) or via the embedded type (e.g., p.Type.Field).
  • Invalid field access causes a compile-time error.

This dot notation ensures simple, type-safe access to struct fields in Go.

What is the new function?h2

In Go, the new function allocates memory for a variable of a given type and returns a pointer to it, initialized to the type’s zero value. It’s a built-in function used to create pointers dynamically.

Syntax:

Go
ptr := new(Type)

Example:

Go
p := new(int) // Allocates an int, returns *int
*p = 42 // Set value
fmt.Println(*p) // Output: 42
fmt.Println(p) // Output: address (e.g., 0xc0000120b8)
type Person struct {
Name string
}
person := new(Person) // Returns *Person, Name is ""
person.Name = "Alice"

Key Points:

  • Returns a pointer (*Type) to a newly allocated, zero-valued instance.
  • Zero values: 0 for numbers, "" for strings, nil for pointers/slices/maps, etc.
  • Less common than struct literals or make (for slices, maps, channels).
  • Useful for allocating pointers to simple types or structs when initialization isn’t needed.
  • Memory is allocated on the heap, managed by Go’s garbage collector.

new provides a concise way to allocate memory for pointers in Go.

How do you use the make function?h2

In Go, the make function is used to initialize and allocate memory for slices, maps, and channels, returning a ready-to-use instance of the specified type. Unlike new, it initializes the data structure with a non-nil value.

Syntax:

Go
make(Type, length, [capacity]) // For slices
make(Type) // For maps, channels

Examples:

  • Slice:
    Go
    s := make([]int, 3, 5) // Length 3, capacity 5, initialized to [0 0 0]
    s[0] = 1
    fmt.Println(s) // [1 0 0]
  • Map:
    Go
    m := make(map[string]int) // Empty map
    m["key"] = 42
    fmt.Println(m) // map[key:42]
  • Channel:
    Go
    ch := make(chan int, 2) // Buffered channel with capacity 2
    ch <- 1
    fmt.Println(<-ch) // 1

Key Points:

  • Used only for slices, maps, and channels; other types use new or literals.
  • Slice make allows specifying length and optional capacity.
  • Map and channel make creates initialized, non-nil instances.
  • Uninitialized slices/maps/channels are nil and cause panics if used.

make ensures proper initialization for dynamic data structures in Go.

What is the difference between new and make?h2

In Go, new and make are built-in functions for memory allocation, but they serve different purposes:

  • new:

    • Allocates memory for any type and returns a pointer (*Type) to it, initialized to the type’s zero value (e.g., 0, "", nil).
    • Used for types like structs, integers, or pointers.
    • Example: p := new(int) returns *int with value 0.
    • Does not initialize internal data structures.
  • make:

    • Initializes and allocates memory for slices, maps, and channels, returning a non-nil instance ready for use.
    • Only works for []Type, map[Key]Value, and chan Type.
    • Example: s := make([]int, 3) creates a slice with length 3, initialized to [0 0 0].
    • Sets up internal structures (e.g., slice length/capacity, map hash table).

Key Differences:

  • Purpose: new allocates with zero values; make initializes slices/maps/channels.
  • Return Type: new returns *Type; make returns Type.
  • Usage: Use new for structs/pointers; make for slices/maps/channels.
  • Example:
    Go
    p := new([]int) // *[]int, points to nil slice
    s := make([]int, 3) // []int, initialized to [0 0 0]

Use new for general allocation, make for specific data structures requiring initialization.

How do you copy a slice?h2

In Go, to copy a slice, you use the built-in copy function, which copies elements from a source slice to a destination slice. Slices are reference types, so direct assignment shares the underlying array, but copy creates a distinct copy.

Syntax:

Go
copy(dst, src []Type) int
  • dst: Destination slice.
  • src: Source slice.
  • Returns the number of elements copied.

Example:

Go
src := []int{1, 2, 3}
dst := make([]int, len(src)) // Create destination slice
copy(dst, src) // Copy elements
dst[0] = 99 // Modify dst, src unchanged
fmt.Println(dst) // [99 2 3]
fmt.Println(src) // [1 2 3]

Key Points:

  • Both slices must be of the same type.
  • Copies the minimum of len(dst) and len(src) elements.
  • Destination slice must be initialized with make or have sufficient capacity.
  • Does not copy the underlying array’s capacity; only elements.
  • Example with partial copy:
    Go
    dst := make([]int, 2)
    copy(dst, []int{1, 2, 3}) // Copies [1 2]

copy ensures independent slices, preventing unintended shared modifications.

What is the capacity of a slice?h2

In Go, the capacity of a slice is the number of elements in the underlying array, starting from the slice’s first element, that the slice can access without reallocation. It’s accessed using the built-in cap function.

Key Points:

  • A slice is defined by a pointer to an array, a length (len), and a capacity (cap).
  • Capacity is the total number of elements the underlying array can hold from the slice’s start.
  • If a slice grows beyond its capacity (e.g., via append), Go allocates a new, larger array.

Example:

Go
s := make([]int, 3, 5) // Length: 3, Capacity: 5
fmt.Println(len(s), cap(s)) // Output: 3 5
s = append(s, 4) // Still fits within capacity
fmt.Println(cap(s)) // Still 5
slice := []int{1, 2, 3, 4, 5}
sub := slice[1:3] // Length: 2 (elements 2, 3), Capacity: 4 (from index 1 to end)
fmt.Println(cap(sub)) // Output: 4

Usage:

  • Use cap to check if append will trigger reallocation.
  • Capacity helps optimize performance by minimizing array resizing.

Capacity ensures efficient slice operations in Go.

How do you append to a slice?h2

In Go, the built-in append function adds one or more elements to the end of a slice and returns a new slice. It may reallocate the underlying array if capacity is exceeded.

Syntax:

Go
newSlice := append(oldSlice, element1, element2, ...)

Examples:

Go
s := []int{1, 2} // Initial slice
s = append(s, 3) // Append single element: [1 2 3]
s = append(s, 4, 5) // Append multiple: [1 2 3 4 5]
// Append a slice
other := []int{6, 7}
s = append(s, other...) // ... spreads the slice: [1 2 3 4 5 6 7]

Key Points:

  • Always assign the result back to a variable (e.g., s = append(s, elem)), as slices are reference types.
  • If capacity is sufficient, it reuses the array; otherwise, it allocates a larger one (typically doubles capacity).
  • Works with any slice type; length increases by the number of appended elements.
  • Efficient for dynamic growth, but pre-allocate with make for known sizes to minimize reallocations.

append makes slices flexible and efficient for growing collections in Go.

What happens when you append beyond capacity?h2

In Go, when you append elements to a slice beyond its capacity, the Go runtime allocates a new, larger underlying array to accommodate the additional elements. The original slice’s data is copied to the new array, and the slice’s pointer, length, and capacity are updated.

Process:

  • If len(slice) < cap(slice), append adds elements within the existing array, increasing the length.
  • If len(slice) >= cap(slice), Go creates a new array (typically double the current capacity or larger), copies the existing elements, and appends the new ones.
  • The original slice remains unchanged unless reassigned to the result of append.

Example:

Go
s := make([]int, 2, 2) // Length: 2, Capacity: 2
s[0], s[1] = 1, 2 // [1 2]
s = append(s, 3) // Exceeds capacity, new array allocated
fmt.Println(s) // [1 2 3]
fmt.Println(cap(s)) // New capacity (e.g., 4)

Key Points:

  • Reallocation is automatic but involves copying, which can impact performance.
  • Pre-allocate capacity with make for efficiency if size is known.
  • Always reassign the result: s = append(s, elem).

This ensures slices remain flexible while managing memory efficiently.

How do you delete a key from a map?h2

In Go, you delete a key from a map using the built-in delete function, which removes the key-value pair from the map.

Syntax:

Go
delete(mapName, key)

Example:

Go
m := map[string]int{"a": 1, "b": 2, "c": 3}
delete(m, "b") // Removes key "b"
fmt.Println(m) // Output: map[a:1 c:3]

Key Points:

  • mapName must be a map; key must match the map’s key type.
  • If the key doesn’t exist, delete does nothing (no error).
  • Maps must be initialized (e.g., with make) before using delete, or a nil map will cause a panic.
  • Deletion is efficient, modifying the map in place.
  • Example with non-existent key:
    Go
    delete(m, "x") // No effect, no panic

The delete function provides a simple, safe way to remove key-value pairs from maps in Go.

What is the ok idiom in maps?h2

In Go, the ok idiom is a pattern used when accessing a map to check if a key exists. When retrieving a value, a map returns the value and a boolean (ok) indicating whether the key was found. This avoids confusion with zero values.

Syntax:

Go
value, ok := mapName[key]

Example:

Go
m := map[string]int{"a": 1, "b": 0}
value, ok := m["a"] // value = 1, ok = true
fmt.Println(value, ok)
value, ok = m["b"] // value = 0, ok = true
fmt.Println(value, ok)
value, ok = m["c"] // value = 0, ok = false
fmt.Println(value, ok)

Key Points:

  • ok is true if the key exists, false if it doesn’t.
  • value is the map’s value for the key or the type’s zero value (e.g., 0 for int) if the key is missing.
  • Prevents errors when distinguishing between a zero value and a missing key.
  • Often used in if statements: if v, ok := m[key]; ok { ... }.

The ok idiom ensures safe and clear map key lookups in Go.

How do you check if a map key exists?h2

In Go, to check if a map key exists, use the ok idiom by accessing the map with a key and capturing the second return value, a boolean (ok), which indicates whether the key is present.

Syntax:

Go
value, ok := mapName[key]
if ok {
// Key exists, use value
}

Example:

Go
m := map[string]int{"a": 1, "b": 2}
if v, ok := m["a"]; ok {
fmt.Println("Key 'a' exists with value:", v) // Output: Key 'a' exists with value: 1
}
if _, ok := m["c"]; !ok {
fmt.Println("Key 'c' does not exist") // Output: Key 'c' does not exist
}

Key Points:

  • ok is true if the key exists, false otherwise.
  • value is the key’s value or the type’s zero value (e.g., 0 for int) if the key is missing.
  • Use _ to ignore the value if only checking existence.
  • Maps must be initialized; accessing a nil map causes a panic.

The ok idiom provides a safe, idiomatic way to verify map key existence in Go.

What are nested maps?h2

In Go, nested maps are maps where the value type is another map, allowing hierarchical key-value storage. They’re useful for representing complex, multi-level data structures like JSON-like objects.

Syntax:

Go
map[keyType1]map[keyType2]valueType

Example:

Go
// Declare a nested map
m := make(map[string]map[string]int)
// Initialize inner map
m["user1"] = make(map[string]int)
m["user1"]["age"] = 30
m["user1"]["score"] = 95
m["user2"] = make(map[string]int)
m["user2"]["age"] = 25
fmt.Println(m["user1"]["age"]) // Output: 30
fmt.Println(m) // map[user1:map[age:30 score:95] user2:map[age:25]]

Key Points:

  • Inner maps must be initialized with make before adding key-value pairs, or a nil map panic occurs.
  • Access values with multiple keys: m[key1][key2].
  • Useful for grouped data (e.g., user profiles, configurations).
  • Check key existence with the ok idiom: if inner, ok := m[key1]; ok { ... }.
  • Nested maps can be deeply nested (e.g., map[key]map[key]map[key]value).

Nested maps provide flexible, dynamic data organization in Go, ideal for hierarchical data.

How do you iterate over a map?h2

In Go, you iterate over a map using a for loop with the range keyword, which provides access to each key-value pair. Maps are unordered, so iteration order is random.

Syntax:

Go
for key, value := range mapName {
// Use key and value
}

Example:

Go
m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
fmt.Printf("Key: %s, Value: %d\n", k, v)
}
// Possible output (order varies):
// Key: a, Value: 1
// Key: b, Value: 2
// Key: c, Value: 3

Key Points:

  • range returns the key and value; use _ to ignore either (e.g., for k, _ := range m).
  • If the map is nil or empty, the loop doesn’t execute.
  • Order of iteration is not guaranteed, as maps are inherently unordered.
  • To iterate over keys only: for k := range m { ... }.
  • Maps must be initialized (e.g., with make) before iteration.

This approach makes map iteration simple and efficient in Go.

What is the order of iteration in maps?h2

In Go, the order of iteration over a map using a for loop with range is random. Maps in Go are inherently unordered data structures, so there is no guaranteed sequence for key-value pairs during iteration.

Example:

Go
m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
fmt.Printf("Key: %s, Value: %d\n", k, v)
}
// Possible output (varies each run):
// Key: b, Value: 2
// Key: c, Value: 3
// Key: a, Value: 1

Key Points:

  • The lack of order ensures implementation flexibility and performance.
  • Iteration order may differ between runs, even with the same map.
  • If order matters, sort keys explicitly:
    Go
    keys := make([]string, 0, len(m))
    for k := range m {
    keys = append(keys, k)
    }
    sort.Strings(keys)
    for _, k := range keys {
    fmt.Println(k, m[k])
    }
  • Random iteration prevents reliance on a specific order, promoting robust code.

This randomness aligns with Go’s design for simplicity and efficiency in map operations.

Explain formatted printing with fmt.Printf.h2

In Go, fmt.Printf is a function from the fmt package used for formatted printing to stdout. It allows you to format output using verbs (placeholders) to control how values are displayed.

Syntax:

Go
fmt.Printf(format string, args ...interface{})

Key Verbs:

  • %v: Default format (generic value representation).
  • %d: Integer (base-10).
  • %s: String.
  • %f: Floating-point (e.g., %.2f for 2 decimal places).
  • %t: Boolean.
  • %p: Pointer address.

Example:

Go
name := "Alice"
age := 30
score := 95.5
fmt.Printf("Name: %s, Age: %d, Score: %.1f\n", name, age, score)
// Output: Name: Alice, Age: 30, Score: 95.5

Key Points:

  • The format string contains text and verbs; arguments match verbs in order.
  • Use %v for structs or unknown types: fmt.Printf("%v", struct{...}).
  • Width and precision: %5d (5 spaces), %.2f (2 decimals).
  • Use \n for newlines; %+v for verbose struct output.
  • Panics if verb and argument types mismatch.

fmt.Printf provides flexible, type-safe formatting for console output in Go.

How do you read input with fmt.Scan?h2

In Go, fmt.Scan reads whitespace-separated input from stdin and stores it in variables passed by pointer.

Syntax:

Go
n, err := fmt.Scan(&var1, &var2, ...)

Example:

Go
var name string
var age int
fmt.Scan(&name, &age) // Input: Alice 30
fmt.Println(name, age) // Output: Alice 30

Key Points:

  • Use pointers (&var) to capture values.
  • Scans until EOF or error; returns count of items and error.
  • Handles basic types; mismatches cause errors.
  • Variants: Scanf for format strings, Scanln for line-based input.
  • For complex input, use bufio.Scanner.

Simple for basic console apps.

What is the os package?h2

The os package in Go provides functions for interacting with the operating system in a platform-independent way. It handles tasks like file operations, environment variables, and process management.

Key Features:

  • File Operations: Create, read, write, and delete files (e.g., os.Open, os.Create, os.Remove).
  • Directory Management: Create or navigate directories (e.g., os.Mkdir, os.Chdir).
  • Environment Variables: Access or set environment variables (e.g., os.Getenv, os.Setenv).
  • Process Control: Manage program execution (e.g., os.Exit, os.Getpid).
  • File Information: Access file metadata (e.g., os.Stat).

Example:

Go
file, err := os.Create("test.txt")
if err != nil {
fmt.Println("Error:", err)
return
}
defer file.Close()
file.WriteString("Hello")
fmt.Println(os.Getenv("PATH")) // Prints PATH variable

Key Points:

  • Functions return errors for explicit handling (e.g., err != nil).
  • Cross-platform, abstracting OS-specific details.
  • Use with io, ioutil, or bufio for advanced I/O.
  • Essential for system-level programming in Go.

The os package simplifies OS interactions for robust applications.

How do you get command-line arguments?h2

In Go, command-line arguments are accessed via the os.Args variable from the os package. It’s a slice of strings ([]string) containing the program’s name (at index 0) and any arguments passed to it.

Example:

Go
package main
import (
"fmt"
"os"
)
func main() {
for i, arg := range os.Args {
fmt.Printf("Arg %d: %s\n", i, arg)
}
}

Running ./program hello world outputs:

Arg 0: ./program
Arg 1: hello
Arg 2: world

Key Points:

  • os.Args[0] is the program’s name; actual arguments start at os.Args[1].
  • Check len(os.Args) to verify argument count.
  • Use the flag package for structured argument parsing (e.g., -name=value).
  • Access is simple but requires manual validation for types or errors.
  • Example: if len(os.Args) > 1 { fmt.Println(os.Args[1]) } to safely access the first argument.

This approach provides direct, flexible access to command-line inputs in Go programs.

What is the flag package?h2

The flag package in Go provides a simple way to parse command-line flags (options) for a program. It allows defining flags with names, default values, and usage descriptions, making it easy to handle structured input from the command line.

Key Features:

  • Defines flags for various types (e.g., string, int, bool).
  • Automatically generates help text (-h or --help).
  • Parses flags from os.Args and stores values in variables.

Example:

Go
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("name", "Guest", "user name")
age := flag.Int("age", 18, "user age")
flag.Parse()
fmt.Printf("Name: %s, Age: %d\n", *name, *age)
}

Running ./program -name=Alice -age=30 outputs: Name: Alice, Age: 30

Key Points:

  • Define flags with flag.Type(name, default, usage).
  • flag.Parse() processes os.Args after flag definitions.
  • Flags are pointers (e.g., *string); dereference to use values.
  • Non-flag arguments are accessible via flag.Args().
  • Use -h to see auto-generated help.

The flag package simplifies robust command-line argument parsing in Go.

How do you parse flags?h2

In Go, flags are parsed using the flag package, which processes command-line arguments into defined variables. You define flags with names, default values, and usage descriptions, then call flag.Parse() to process them from os.Args.

Steps:

  1. Define flags using flag.Type(name, default, usage).
  2. Call flag.Parse() to parse os.Args.
  3. Access flag values via pointers.

Example:

Go
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("name", "Guest", "user name")
age := flag.Int("age", 18, "user age")
verbose := flag.Bool("verbose", false, "enable verbose output")
flag.Parse()
fmt.Printf("Name: %s, Age: %d, Verbose: %t\n", *name, *age, *verbose)
}

Running ./program -name=Alice -age=30 -verbose outputs: Name: Alice, Age: 30, Verbose: true

Key Points:

  • Flags are defined as pointers (e.g., *string, *int); dereference to use (*name).
  • flag.Parse() processes flags starting with - or --.
  • Non-flag arguments are available via flag.Args().
  • Use -h for auto-generated help text.
  • Supports custom flags via flag.Value interface.

This approach ensures structured, user-friendly command-line parsing in Go.

What is the math package?h2

The math package in Go provides mathematical functions and constants for numerical operations, primarily for floating-point types (float32, float64). It’s part of the standard library and used for tasks like trigonometry, exponentiation, and rounding.

Key Features:

  • Constants: math.Pi, math.E, math.MaxFloat64, etc.
  • Functions:
    • Basic: math.Abs, math.Max, math.Min.
    • Trigonometric: math.Sin, math.Cos, math.Tan.
    • Exponential/Logarithmic: math.Pow, math.Log, math.Sqrt.
    • Rounding: math.Floor, math.Ceil, math.Round.

Example:

Go
import "math"
func main() {
fmt.Println(math.Pi) // 3.141592653589793
fmt.Println(math.Sqrt(16)) // 4
fmt.Println(math.Pow(2, 3)) // 8
fmt.Println(math.Floor(3.7)) // 3
}

Key Points:

  • Works with float64 inputs/outputs; convert float32 or int explicitly.
  • Some functions (e.g., math.NaN, math.IsInf) handle special cases.
  • Use math/big for arbitrary-precision arithmetic.
  • Thread-safe and efficient for numerical computations.

The math package is essential for scientific, financial, or geometric calculations in Go.

How do you generate random numbers?h2

In Go, random numbers are generated using the math/rand package, which provides pseudo-random number generators. For cryptographic purposes, use crypto/rand.

Using math/rand:

  1. Seed the Generator: Set a seed for reproducibility (default is fixed, so use time.Now() for variability).
  2. Generate Numbers:
    • rand.Intn(n): Returns a random int in [0, n).
    • rand.Float64(): Returns a random float64 in [0.0, 1.0).

Example:

Go
import (
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano()) // Set seed
fmt.Println(rand.Intn(100)) // Random int: 0-99
fmt.Println(rand.Float64()) // Random float: 0.0-1.0
}

Key Points:

  • Seed with rand.Seed for different sequences; time.Now().UnixNano() ensures uniqueness.
  • Use crypto/rand for secure random numbers (e.g., keys):
    Go
    import "crypto/rand"
    b := make([]byte, 16)
    rand.Read(b) // Cryptographically secure bytes
  • math/rand is deterministic with the same seed.
  • Thread-safe if seeded once at program start.

This approach provides flexible random number generation for Go applications.

What is the time package?h2

The time package in Go provides functionality for working with time and dates, including measuring time, formatting, parsing, and handling durations. It’s part of the standard library and essential for tasks like scheduling, logging, or timeouts.

Key Features:

  • Time Representation: time.Time for specific points in time (e.g., now, future).
  • Duration: time.Duration for time intervals (e.g., milliseconds, hours).
  • Functions:
    • time.Now(): Current time.
    • time.Sleep(d): Pause execution.
    • time.Parse(layout, value): Parse string to time.Time.
    • time.Format(layout): Format time to string.
  • Constants: Layouts like time.RFC3339 for formatting/parsing.

Example:

Go
import "time"
func main() {
now := time.Now() // Current time
fmt.Println(now.Format(time.RFC3339)) // e.g., 2025-10-01T10:09:00-03:00
time.Sleep(1 * time.Second) // Pause 1 second
duration := 2 * time.Hour
later := now.Add(duration)
fmt.Println(later) // Time after 2 hours
}

Key Points:

  • Time zones are supported via time.Location.
  • Use time.After or time.Tick for timers in concurrency.
  • Thread-safe and precise for time-related operations.

The time package is vital for managing time in Go applications.

How do you get the current time?h2

In Go, you get the current time using the time.Now function from the time package, which returns a time.Time value representing the current date and time in the system’s local time zone.

Example:

Go
import "time"
func main() {
current := time.Now()
fmt.Println(current) // e.g., 2025-10-01 10:09:00 -0300 -03
fmt.Println(current.Format("2006-01-02 15:04:05")) // e.g., 2025-10-01 10:09:05
}

Key Points:

  • time.Now() includes date, time, and time zone.
  • Use time.Format(layout) to format output (e.g., "2006-01-02" for date only).
  • Access components like current.Year(), current.Hour(), or current.Second().
  • For UTC, use current.UTC().
  • Thread-safe and precise for system time queries.

This method is simple and widely used for logging, scheduling, or timestamps in Go.

What is a duration in Go?h2

In Go, a duration is a type (time.Duration) from the time package that represents a time interval, measured in nanoseconds as an int64. It’s used to express differences between two time.Time values or to specify time spans for operations like timeouts or sleeps.

Example:

Go
import "time"
func main() {
d := 2 * time.Second // Duration of 2 seconds
fmt.Println(d) // Output: 2s
time.Sleep(d) // Pause for 2 seconds
start := time.Now()
time.Sleep(1 * time.Millisecond)
elapsed := time.Since(start) // Duration since start
fmt.Println(elapsed) // e.g., 1.234ms
}

Key Points:

  • Defined using constants like time.Nanosecond, time.Microsecond, time.Millisecond, time.Second, time.Minute, time.Hour.
  • Arithmetic operations: Add/subtract with time.Add(duration) or time.Sub.
  • Use time.Since(t) to get duration from a past time.Time.
  • Convert to other units: d.Seconds() or d.Nanoseconds().
  • Thread-safe and precise for timing tasks.

Durations are essential for scheduling, timeouts, and measuring time intervals in Go.

How do you sleep in Go?h2

In Go, you pause program execution for a specified duration using the time.Sleep function from the time package. It takes a time.Duration argument and halts the current goroutine until the duration elapses.

Syntax:

Go
time.Sleep(duration time.Duration)

Example:

Go
import "time"
func main() {
fmt.Println("Start:", time.Now().Format("15:04:05"))
time.Sleep(2 * time.Second) // Pause for 2 seconds
fmt.Println("End:", time.Now().Format("15:04:05"))
}
// Output:
// Start: 10:10:05
// End: 10:10:07

Key Points:

  • Duration is specified using time.Duration constants (e.g., time.Second, time.Millisecond).
  • Only pauses the current goroutine; other goroutines continue running.
  • Non-blocking for other concurrent tasks.
  • Useful for delays, rate limiting, or simulating latency.
  • For more complex timing, consider time.After or time.Tick in goroutines.

time.Sleep provides a simple, precise way to introduce delays in Go programs.

What is the strings package?h2

The strings package in Go provides functions for manipulating strings, such as searching, splitting, joining, and comparing. It’s part of the standard library and handles common text operations efficiently.

Key Functions:

  • strings.Split(s, sep): Splits string into a slice.
  • strings.Join(slice, sep): Joins slice into a string.
  • strings.Contains(s, substr): Checks if substring exists.
  • strings.Replace(s, old, new, count): Replaces substrings.
  • strings.ToUpper(s), strings.ToLower(s): Case conversion.
  • strings.Trim(s, cutset): Removes characters from ends.

Example:

Go
import "strings"
s := "hello, world"
parts := strings.Split(s, ",") // ["hello ", " world"]
result := strings.Join(parts, "-") // "hello - world"
fmt.Println(strings.ToUpper(result)) // "HELLO - WORLD"

Key Points:

  • Operates on immutable strings; returns new strings.
  • UTF-8 aware but byte-based; use unicode package for runes.
  • Efficient for most string tasks; use strings.Builder for concatenation.

The package simplifies string handling in Go programs.

How do you split a string?h2

In Go, you split a string into a slice of substrings using the strings.Split function from the strings package. It divides the string based on a specified delimiter.

Syntax:

Go
strings.Split(s string, sep string) []string

Example:

Go
import "strings"
s := "apple,banana,orange"
parts := strings.Split(s, ",") // Returns []string{"apple", "banana", "orange"}
fmt.Println(parts)
// Empty separator splits into runes
runes := strings.Split("abc", "") // []string{"a", "b", "c"}

Key Points:

  • Returns a []string containing substrings split by sep.
  • If sep is empty, splits into individual runes (characters).
  • If no delimiter is found, returns a single-element slice with the original string.
  • Use strings.SplitN(s, sep, n) to limit splits to n substrings.
  • UTF-8 safe; handles Unicode correctly.

strings.Split is a simple, efficient way to tokenize strings in Go.

What is the bytes package?h2

The bytes package in Go provides functions for manipulating byte slices ([]byte), similar to the strings package for strings. It’s part of the standard library and is used for tasks like searching, splitting, joining, or modifying byte sequences, often in I/O or binary data processing.

Key Functions:

  • bytes.Split(s, sep): Splits byte slice into subslices.
  • bytes.Join(slices, sep): Joins byte slices with a separator.
  • bytes.Contains(s, subslice): Checks if a subslice exists.
  • bytes.Replace(s, old, new, n): Replaces subslice matches.
  • bytes.ToUpper(s), bytes.ToLower(s): Case conversion for ASCII bytes.

Example:

Go
import "bytes"
b := []byte("hello,world")
parts := bytes.Split(b, []byte(",")) // [][]byte{[]byte("hello"), []byte("world")}
result := bytes.Join(parts, []byte("-")) // []byte("hello-world")
fmt.Println(string(result)) // Output: hello-world

Key Points:

  • Operates on mutable []byte, unlike immutable strings.
  • Functions mirror strings package but work with bytes.
  • Ideal for I/O, buffers, or binary data.
  • Use string(b) or []byte(s) for conversions.

The bytes package simplifies efficient byte manipulation in Go.

How do you compare strings?h2

In Go, strings are compared using comparison operators (==, !=, <, <=, >, >=) or functions from the strings package. Comparisons are based on lexicographical (alphabetical) order, using UTF-8 byte sequences.

Using Operators:

  • == and !=: Check for equality or inequality.
  • <, <=, >, >=: Compare byte-by-byte based on Unicode code points.
Go
s1, s2 := "apple", "banana"
fmt.Println(s1 == s2) // false
fmt.Println(s1 < s2) // true (apple comes before banana)

Using strings Package:

  • strings.Compare(a, b): Returns -1 (a < b), 0 (a == b), or 1 (a > b).
  • strings.EqualFold(a, b): Case-insensitive comparison.
Go
fmt.Println(strings.Compare("apple", "banana")) // -1
fmt.Println(strings.EqualFold("Apple", "apple")) // true

Key Points:

  • Operators are simple and efficient for direct comparisons.
  • strings.Compare is useful for explicit ordering.
  • strings.EqualFold ignores case (ASCII/Unicode-aware).
  • UTF-8 ensures proper handling of Unicode strings.
  • Empty strings are less than non-empty strings.

This approach provides flexible, type-safe string comparisons in Go.

What is UTF-8 encoding in Go?h2

In Go, UTF-8 encoding is the default encoding for strings, representing Unicode characters as a variable-length sequence of bytes (1–4 bytes per character). Each string is a sequence of UTF-8-encoded Unicode code points, ensuring compatibility with all text, including ASCII and multilingual characters.

Key Points:

  • Variable Length: ASCII characters (e.g., ‘A’) use 1 byte; others (e.g., ‘世’) use 2–4 bytes.
  • String Handling: Strings are immutable byte sequences; indexing (s[i]) returns a byte.
  • Runes: Use rune (alias for int32) for individual Unicode code points; convert with []rune(s).
  • Packages:
    • unicode/utf8: Functions like RuneCountInString(s) for character count or Valid(s) to check UTF-8 validity.
    • Example: len("世界") returns 6 (bytes); utf8.RuneCountInString("世界") returns 2 (runes).

Example:

Go
s := "Hello, 世界"
for i, r := range s {
fmt.Printf("Index: %d, Rune: %c\n", i, r)
}
// Output: Index: 0, Rune: H; ...; Index: 7, Rune: 世

UTF-8 ensures Go handles text efficiently and supports global character sets.

How do you convert between string and rune?h2

In Go, converting between a string and rune is straightforward, as strings are UTF-8 encoded byte sequences, and a rune (alias for int32) represents a single Unicode code point.

String to Rune:

  • Convert a string to a slice of runes using []rune(s) to get individual Unicode code points.
  • Access a single rune via indexing or range loop.
Go
s := "Hello, 世界"
runes := []rune(s) // []rune{'H', 'e', ..., '世', '界'}
fmt.Println(runes[0]) // 72 (Unicode for 'H')
for _, r := range s { // Iterates runes
fmt.Println(r) // Prints code points
}

Rune to String:

  • Convert a single rune to a string using string(r) or fmt.Sprintf.
  • Convert a slice of runes to a string using string(runes).
Go
r := rune('A') // 65
s := string(r) // "A"
runes := []rune{'H', 'i'}
str := string(runes) // "Hi"

Key Points:

  • []rune(s) handles UTF-8 correctly; len(s) counts bytes, not runes.
  • Use unicode/utf8 for advanced rune operations (e.g., RuneCountInString).
  • Conversions are explicit and safe.

This ensures proper handling of Unicode text in Go.

What is the sort package?h2

The sort package in Go provides functions and types for sorting slices and custom data structures. It’s part of the standard library and supports sorting various types efficiently.

Key Features:

  • Functions:
    • sort.Ints, sort.Float64s, sort.Strings: Sort slices of built-in types.
    • sort.Slice: Sorts any slice using a custom comparison function.
    • sort.Search: Binary search for sorted slices.
  • Interface: sort.Interface for custom sorting, requiring Len(), Less(i, j), and Swap(i, j) methods.

Example:

Go
import "sort"
func main() {
nums := []int{3, 1, 2}
sort.Ints(nums) // Sorts in ascending order
fmt.Println(nums) // [1 2 3]
// Custom sort with sort.Slice
sort.Slice(nums, func(i, j int) bool {
return nums[i] > nums[j] // Descending order
})
fmt.Println(nums) // [3 2 1]
}

Key Points:

  • Sorts in-place, modifying the original slice.
  • Stable sorting available via sort.Stable.
  • Use sort.Search for efficient searching in sorted data.
  • Thread-safe for read-only operations; synchronize for concurrent use.

The sort package simplifies sorting tasks in Go.

How do you sort a slice?h2

In Go, you sort a slice using the sort package, which provides functions for sorting slices of built-in types or custom types. The slice is sorted in-place.

Methods:

  • Built-in Types:

    • Use sort.Ints, sort.Float64s, or sort.Strings for slices of int, float64, or string.
    Go
    import "sort"
    nums := []int{3, 1, 2}
    sort.Ints(nums) // Ascending: [1 2 3]
  • Custom Sorting:

    • Use sort.Slice with a comparison function for any slice type.
    Go
    nums := []int{3, 1, 2}
    sort.Slice(nums, func(i, j int) bool {
    return nums[i] > nums[j] // Descending: [3 2 1]
    })
  • Custom Types:

    • Implement sort.Interface (Len, Less, Swap) for complex types.
    Go
    type Person struct { Age int }
    type ByAge []Person
    func (a ByAge) Len() int { return len(a) }
    func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
    func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    people := []Person{{Age: 30}, {Age: 20}}
    sort.Sort(ByAge(people)) // Sorts by Age

Key Points:

  • Sorting modifies the original slice.
  • Use sort.Stable for stable sorting.
  • Ensure the slice is initialized to avoid panics.

This approach ensures efficient, flexible sorting in Go.

What is a custom sort?h2

In Go, a custom sort allows sorting a slice using a user-defined comparison logic, typically implemented with the sort package. It’s useful for sorting complex types or non-standard orders (e.g., descending, by specific fields).

Methods:

  1. Using sort.Slice:

    • Provide a comparison function to sort.Slice for any slice.
    Go
    import "sort"
    nums := []int{3, 1, 2}
    sort.Slice(nums, func(i, j int) bool {
    return nums[i] > nums[j] // Descending order
    })
    fmt.Println(nums) // [3 2 1]
  2. Using sort.Interface:

    • Implement Len(), Less(i, j), and Swap(i, j) for custom types.
    Go
    type Person struct { Age int }
    type ByAge []Person
    func (a ByAge) Len() int { return len(a) }
    func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
    func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    people := []Person{{Age: 30}, {Age: 20}}
    sort.Sort(ByAge(people)) // Sorts by Age

Key Points:

  • sort.Slice is simpler for quick custom sorting.
  • sort.Interface is reusable for complex types.
  • Sorting is in-place; use sort.Stable for stable sorting.
  • Thread-safe for reads; synchronize for concurrent writes.

Custom sorts provide flexibility for tailored sorting in Go.

How do you search in a sorted slice?h2

In Go, you search a sorted slice using the sort.Search function from the sort package, which performs a binary search to find the index of an element or where it should be inserted. The slice must be sorted in ascending order.

Syntax:

Go
index := sort.Search(len(slice), func(i int) bool {
return slice[i] >= target
})

Example:

Go
import "sort"
func main() {
nums := []int{1, 3, 5, 7, 9} // Sorted slice
target := 5
index := sort.Search(len(nums), func(i int) bool {
return nums[i] >= target
})
if index < len(nums) && nums[index] == target {
fmt.Printf("Found %d at index %d\n", target, index) // Output: Found 5 at index 2
} else {
fmt.Printf("%d not found; insert at %d\n", target, index)
}
}

Key Points:

  • Returns the smallest index where slice[i] >= target or len(slice) if not found.
  • Use sort.Ints, sort.Float64s, or sort.Strings for specific types.
  • Slice must be sorted; unsorted data gives incorrect results.
  • Time complexity is O(log n), efficient for large slices.

sort.Search provides fast, reliable searching in sorted slices.

What is the io package?h2

The io package in Go provides basic interfaces and primitives for input/output operations, enabling reading from and writing to streams like files or networks. It’s foundational for higher-level packages like bufio or net/http.

Key Interfaces:

  • io.Reader: Defines Read(p []byte) (n int, err error) for reading data.
  • io.Writer: Defines Write(p []byte) (n int, err error) for writing data.
  • io.ReadWriter: Combines Reader and Writer.
  • io.Closer: Defines Close() error for resource cleanup.

Example:

Go
import "io"
func main() {
r := strings.NewReader("hello")
p := make([]byte, 5)
n, _ := io.ReadFull(r, p) // Read 5 bytes
fmt.Println(string(p)) // "hello"
}

Key Points:

  • Promotes composable I/O (e.g., implement Reader for custom sources).
  • Returns io.EOF at end of input.
  • Thread-safe where applicable; used extensively in standard library.

The package ensures portable, efficient I/O handling in Go.

How do you read from stdin?h2

In Go, you read from stdin (standard input) using the os.Stdin file handle, which implements the io.Reader interface, or by using functions from the fmt or bufio packages for specific input patterns.

Methods:

  1. Using fmt.Scan:

    • Reads whitespace-separated input into variables.
    Go
    var name string
    var age int
    fmt.Scan(&name, &age) // Input: Alice 30
    fmt.Println(name, age) // Output: Alice 30
  2. Using bufio.Scanner (Recommended for Lines):

    • Reads input line-by-line or with custom splitting.
    Go
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
    fmt.Println(scanner.Text()) // Reads each line
    }
  3. Using os.Stdin.Read:

    • Reads raw bytes directly.
    Go
    b := make([]byte, 100)
    n, err := os.Stdin.Read(b)
    fmt.Println(string(b[:n])) // Prints input bytes

Key Points:

  • fmt.Scan is simple but limited to basic types.
  • bufio.Scanner is ideal for text or line-based input.
  • Check errors (e.g., io.EOF, scanner.Err()).
  • os.Stdin integrates with io.Reader for flexibility.

Choose bufio.Scanner for robust, line-based stdin reading in Go.

What is a reader interface?h2

In Go, the io.Reader interface defines a standard way to read data from a source, such as files, network connections, or strings. It’s part of the io package and widely used for stream-based input.

Definition:

Go
type Reader interface {
Read(p []byte) (n int, err error)
}
  • Read fills a byte slice p with data from the source, returning the number of bytes read (n) and an error (err).

Example:

Go
import (
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello")
p := make([]byte, 3)
n, err := r.Read(p)
if err == nil {
fmt.Println(string(p[:n])) // Output: Hel
}
}

Key Points:

  • Implementations include os.File, strings.Reader, bytes.Buffer, etc.
  • Returns io.EOF when no more data is available.
  • Allows composable I/O; you can chain readers (e.g., bufio.NewReader).
  • Thread-safe if the underlying source is safe.
  • Used extensively in libraries like net/http or io/ioutil.

The io.Reader interface ensures flexible, reusable input handling in Go.

How do you write to a file?h2

In Go, you write to a file using the os package, typically with os.Create or os.OpenFile, and the io or bufio packages for efficient writing.

Example:

Go
import "os"
func main() {
file, err := os.Create("output.txt") // Creates or overwrites file
if err != nil {
fmt.Println("Error:", err)
return
}
defer file.Close() // Ensure file closes
_, err = file.WriteString("Hello, Go!") // Write string
if err != nil {
fmt.Println("Error:", err)
}
}

Key Points:

  • Use os.Create for new/overwriting files; os.OpenFile for appending (os.O_APPEND).
  • WriteString or Write([]byte) writes data; returns bytes written and error.
  • Use bufio.NewWriter(file) for buffered writing to reduce system calls:
    Go
    w := bufio.NewWriter(file)
    w.WriteString("Buffered write")
    w.Flush() // Ensure data is written
  • Always check errors and close files with defer file.Close().
  • Thread-safe if synchronized; use os.File methods for control.

This approach ensures reliable file writing in Go.

What is the bufio package?h2

The bufio package in Go provides buffered I/O operations, wrapping io.Reader and io.Writer to reduce direct system calls, improving performance for reading and writing data, such as files or stdin/stdout.

Key Features:

  • Buffered Reading: bufio.Reader buffers input for efficient reads.
  • Buffered Writing: bufio.Writer buffers output before writing.
  • Scanner: bufio.Scanner simplifies reading text line-by-line or by tokens.

Example:

Go
import "bufio"
func main() {
// Buffered writing
file, _ := os.Create("output.txt")
defer file.Close()
w := bufio.NewWriter(file)
w.WriteString("Hello")
w.Flush() // Write buffer to file
// Buffered reading
r := bufio.NewReader(os.Stdin)
line, _ := r.ReadString('\n')
fmt.Println(line)
}

Key Points:

  • Buffering minimizes system calls, boosting efficiency.
  • Use Flush() on bufio.Writer to ensure data is written.
  • bufio.Scanner is ideal for line-based input (e.g., scanner.Scan()).
  • Thread-safe if used sequentially; synchronize for concurrent access.

The bufio package optimizes I/O performance in Go programs.

How do you use buffered I/O?h2

In Go, buffered I/O is handled using the bufio package, which wraps io.Reader and io.Writer to reduce system calls by buffering data, improving performance for operations like file or network I/O.

Key Types:

  • bufio.Reader: Buffers input for efficient reading.
  • bufio.Writer: Buffers output before writing.
  • bufio.Scanner: Reads text line-by-line or by tokens.

Example:

Go
import (
"bufio"
"os"
)
func main() {
// Buffered writing
file, _ := os.Create("output.txt")
defer file.Close()
w := bufio.NewWriter(file)
w.WriteString("Hello, Go!")
w.Flush() // Ensure data is written
// Buffered reading
file, _ = os.Open("output.txt")
defer file.Close()
r := bufio.NewReader(file)
data, _ := r.ReadString('\n')
fmt.Println(data) // Output: Hello, Go!
}

Key Points:

  • Use bufio.NewWriter for writing; call Flush() to write buffer to destination.
  • Use bufio.NewReader for reading; methods like ReadString(delim) simplify text input.
  • bufio.Scanner is ideal for line-based reading: scanner.Scan(), scanner.Text().
  • Buffering reduces system calls, enhancing performance.
  • Thread-safe if used sequentially; synchronize for concurrent access.

Buffered I/O optimizes efficiency for I/O-heavy tasks in Go.

What is the path package?h2

The path package in Go provides functions for manipulating file paths in a platform-independent way. It handles path components like directories and filenames, ensuring compatibility across operating systems (e.g., / on Unix, \ on Windows).

Key Functions:

  • path.Join: Combines path components into a single path (e.g., path.Join("dir", "file.txt")dir/file.txt).
  • path.Base: Returns the last element of a path (e.g., path.Base("dir/file.txt")file.txt).
  • path.Dir: Returns the directory part (e.g., path.Dir("dir/file.txt")dir).
  • path.Ext: Returns the file extension (e.g., path.Ext("file.txt").txt).
  • path.Split: Splits path into directory and file (e.g., path.Split("dir/file.txt")dir/, file.txt).

Example:

Go
import "path"
func main() {
p := path.Join("dir", "file.txt")
fmt.Println(p) // Output: dir/file.txt
fmt.Println(path.Base(p)) // Output: file.txt
fmt.Println(path.Dir(p)) // Output: dir
}

Key Points:

  • Handles platform-specific separators (/ or \).
  • Use path/filepath for file system operations (e.g., absolute paths).
  • Thread-safe and simple for path manipulation.

The path package ensures portable path handling in Go.

How do you join paths?h2

In Go, you join paths using the path.Join function from the path package or filepath.Join from the path/filepath package. Both combine path components into a single path, using the platform-specific separator (/ on Unix, \ on Windows).

Syntax:

Go
import "path" // or "path/filepath"
result := path.Join(elem1, elem2, ...)

Example:

Go
import "path"
func main() {
p := path.Join("dir1", "dir2", "file.txt")
fmt.Println(p) // Output: dir1/dir2/file.txt (Unix) or dir1\dir2\file.txt (Windows)
}

Using path/filepath:

Go
import "path/filepath"
p := filepath.Join("dir1", "dir2", "file.txt")
fmt.Println(p) // Same output, platform-specific

Key Points:

  • Automatically handles platform-specific separators.
  • Cleans redundant separators (e.g., path.Join("dir//", "file")dir/file).
  • filepath.Join is preferred for file system operations, as it respects the OS’s conventions.
  • Empty elements are ignored (e.g., path.Join("", "file")file).
  • Thread-safe and simple to use.

Use path.Join or filepath.Join for portable, clean path construction in Go.

What is the filepath package?h2

The filepath package in Go provides platform-independent functions for manipulating file system paths, handling differences in path separators (/ on Unix, \ on Windows) and other OS-specific conventions.

Key Functions:

  • filepath.Join: Combines path components (e.g., filepath.Join("dir", "file.txt")dir/file.txt or dir\file.txt).
  • filepath.Base: Returns the last path element (e.g., file.txt).
  • filepath.Dir: Returns the directory (e.g., dir).
  • filepath.Abs: Resolves relative paths to absolute paths.
  • filepath.Walk: Recursively traverses a directory tree.

Example:

Go
import "path/filepath"
func main() {
p := filepath.Join("dir", "file.txt")
fmt.Println(p) // Output: dir/file.txt (Unix) or dir\file.txt (Windows)
fmt.Println(filepath.Base(p)) // file.txt
fmt.Println(filepath.Dir(p)) // dir
}

Key Points:

  • Unlike the path package, filepath respects OS-specific rules (e.g., drive letters on Windows).
  • Thread-safe and portable across platforms.
  • Use for file system operations; path for generic path strings.
  • Handles edge cases like redundant separators or relative paths.

The filepath package ensures robust, cross-platform path handling in Go.

How do you walk a directory?h2

In Go, you walk a directory using filepath.Walk from the path/filepath package, which recursively traverses a directory tree and applies a user-defined function to each file or directory.

Syntax:

Go
func Walk(root string, walkFn WalkFunc) error
type WalkFunc func(path string, info os.FileInfo, err error) error

Example:

Go
import (
"fmt"
"path/filepath"
"os"
)
func main() {
err := filepath.Walk("mydir", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
fmt.Printf("Path: %s, IsDir: %v\n", path, info.IsDir())
return nil
})
if err != nil {
fmt.Println("Error:", err)
}
}

Key Points:

  • root is the starting directory path.
  • walkFn is called for each file/directory with its path, os.FileInfo, and any error.
  • Return filepath.SkipDir in walkFn to skip a directory; return nil to continue.
  • Thread-safe for reads; synchronize for writes.
  • Handles symbolic links and platform-specific paths.

filepath.Walk simplifies recursive directory traversal in Go.

What is the errors package for basic errors?h2

The errors package in Go provides functions for creating and handling basic errors. It’s part of the standard library and is used to create simple error values and inspect them.

Key Functions:

  • errors.New(text string): Creates an error with a given message.
  • errors.Is(err, target error): Checks if an error matches a target error.
  • errors.As(err, target interface{}): Checks if an error is of a specific type.
  • errors.Join(...error): Combines multiple errors into one.

Example:

Go
import "errors"
func main() {
err := errors.New("something went wrong")
if err != nil {
fmt.Println(err) // Output: something went wrong
}
var notFound = errors.New("not found")
err2 := notFound
fmt.Println(errors.Is(err2, notFound)) // true
}

Key Points:

  • Errors are values implementing the error interface (Error() string).
  • errors.New creates lightweight, immutable errors.
  • Use fmt.Errorf for formatted error messages.
  • errors.Is and errors.As help with error comparison and type checking.
  • Simple and efficient for basic error handling.

The errors package is foundational for creating and managing errors in Go.

Conclusionh2

This series on “100 Basic Go (Golang) Interview Questions” has provided a comprehensive exploration of fundamental concepts and techniques essential for mastering Go, particularly for backend development. Covering topics such as variables, data types, control structures, functions, slices, maps, and key standard library packages like os, fmt, strings, bytes, time, sort, path/filepath, bufio, io, and errors, this guide has equipped you with the knowledge to confidently tackle common interview questions. By understanding Go’s type system, concurrency model, and idiomatic practices—such as using strings.Builder for efficient string concatenation, leveraging sort.Interface for custom sorting, or handling errors with errors.Is—you’re well-prepared to demonstrate proficiency in building robust, efficient backend applications. Whether it’s manipulating UTF-8 strings, managing file I/O, or implementing structured error handling, these foundational skills will help you excel in technical interviews and real-world Go development projects.