*** 1. How Does the Go Scheduler Work?
In Go Programming Language, the scheduler manages the execution of goroutines efficiently over operating system threads.
Go uses the:
- G-M-P model
Components of Scheduler
| Component | Meaning |
|---|---|
| G | Goroutine |
| M | OS Thread (Machine) |
| P | Processor/Logical Processor |
How It Works
- Goroutines (
G) are assigned to processors (P) - Processors execute goroutines using OS threads (
M) - Scheduler balances workloads automatically
Key Features
- Lightweight concurrency
- Fast context switching
- Efficient CPU utilization
Interview Insight
Most interviewers expect:
- Understanding of G-M-P model
- Difference between goroutines and threads
- Work-stealing scheduler concept
*** 2. Difference Between sync.Mutex and sync.RWMutex
Both are synchronization primitives used to protect shared resources.
Difference Table
| Feature | sync.Mutex | sync.RWMutex |
|---|---|---|
| Access Type | One reader/writer | Multiple readers, one writer |
| Read Performance | Lower | Better for read-heavy workloads |
| Complexity | Simple | Slightly complex |
sync.Mutex Example
var mu sync.Mutex
mu.Lock()
// critical section
mu.Unlock()
sync.RWMutex Example
var mu sync.RWMutex
mu.RLock()
// read operation
mu.RUnlock()
mu.Lock()
// write operation
mu.Unlock()
Interview Insight
Use:
-
Mutex→ balanced read/write workloads -
RWMutex→ read-heavy applications
*** 3. How Do You Prevent Deadlocks?
A deadlock occurs when goroutines wait indefinitely for resources.
Common Prevention Techniques
1. Always Unlock Mutexes
Use defer for safe unlocking.
mu.Lock()
defer mu.Unlock()
2. Avoid Circular Waiting
Do not create dependencies between goroutines.
3. Close Channels Properly
Improper channel handling can cause blocking.
4. Use Buffered Channels Carefully
Avoid full-buffer blocking situations.
5. Use Timeouts
Use select with time.After().
Example
select {
case msg := <-ch:
fmt.Println(msg)
case <-time.After(time.Second):
fmt.Println("Timeout")
}
Interview Insight
Deadlocks are common in:
- Improper channel communication
- Incorrect mutex usage
*** 4. How Do You Avoid Goroutine Leaks?
A goroutine leak happens when goroutines continue running unnecessarily and never terminate.
Prevention Techniques
1. Close Channels Properly
close(ch)
2. Use Context Cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
3. Avoid Infinite Blocking
Prevent goroutines from waiting forever.
4. Use WaitGroup
Ensure goroutines complete correctly.
Common Causes
- Unclosed channels
- Infinite loops
- Blocked sends/receives
Interview Insight
Goroutine leaks can cause:
- Memory issues
- Resource exhaustion
- Performance degradation
*** 5. Which Is Safer for Concurrent Access: Channels or Maps?
Channels are safer for concurrent communication because they are designed for synchronization.
Regular Go maps are not safe for concurrent writes.
Unsafe Map Example
m := make(map[string]int)
Concurrent writes can cause:
fatal error: concurrent map writes
Safer Alternatives
- Channels
-
sync.Mutex -
sync.Map
Interview Insight
Best answer:
“Channels are concurrency-safe for communication, while normal maps require synchronization.”
*** 6. What Are Race Conditions and How Do You Detect Them?
A race condition occurs when multiple goroutines access shared data simultaneously and at least one modifies it.
Example
count++
Concurrent updates may produce unpredictable results.
Detecting Race Conditions
Go provides the race detector.
Command
go run -race main.go
or
go test -race
Prevention Techniques
- Mutex
- Channels
- Atomic operations
Interview Insight
Race detection is one of Go’s strongest debugging features.
*** 7. What Is a Worker Pool?
A worker pool is a concurrency pattern where multiple worker goroutines process tasks from a shared queue.
Benefits
- Controlled concurrency
- Better resource management
- Improved scalability
Common Use Cases
- API processing
- Background jobs
- Task queues
Worker Pool Structure
| Component | Purpose |
|---|---|
| Jobs Channel | Receives tasks |
| Workers | Process tasks |
| Results Channel | Returns output |
Interview Insight
Worker pools prevent excessive goroutine creation.
*** 8. How Do You Implement a Worker Pool?
A worker pool is implemented using:
- Goroutines
- Channels
- WaitGroup
Example
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Println("Worker", id, "processing", job)
}
}
func main() {
jobs := make(chan int, 5)
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, jobs, &wg)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
}
Key Concepts
- Job distribution
- Concurrent processing
- Synchronization
Interview Insight
Worker pools are frequently asked in backend and system-design interviews.
** 9. What Is a Pipeline in Go?
A pipeline is a sequence of stages connected using channels where output from one stage becomes input for the next stage.
Example Flow
Input -> Process -> Output
Features
- Concurrent processing
- Separation of concerns
- Better scalability
Example
func square(in <-chan int, out chan<- int) {
for n := range in {
out <- n * n
}
close(out)
}
Interview Insight
Pipelines are heavily used in:
- Data processing
- Streaming systems
- ETL workflows
** 10. What Is sync.Pool?
sync.Pool is used to temporarily store reusable objects to reduce memory allocations and improve performance.
Example
var pool = sync.Pool{
New: func() interface{} {
return "New Object"
},
}
Using Pool
obj := pool.Get()
pool.Put(obj)
Benefits
- Reduced garbage collection pressure
- Better performance
- Object reuse
Common Use Cases
- Buffers
- Temporary objects
- High-performance applications
Interview Insight
sync.Pool is mainly used in:
- Performance-critical systems
- High-throughput applications
** 11. What Is GOMAXPROCS?
In Go Programming Language, GOMAXPROCS controls the maximum number of CPU threads that can execute Go code simultaneously.
It determines how many OS threads can run goroutines in parallel.
Example
runtime.GOMAXPROCS(4)
Important Points
- Default value = number of CPU cores
- Controls parallelism, not concurrency
- Managed by the Go scheduler
Package Required
import "runtime"
Interview Insight
Common interview question:
“Difference between concurrency and parallelism?”
- Concurrency → multiple tasks managed together
- Parallelism → tasks executed simultaneously
** 12. What Is runtime.Gosched()?
runtime.Gosched() voluntarily yields the processor, allowing other goroutines to run.
Example
runtime.Gosched()
Purpose
- Improves goroutine scheduling
- Prevents CPU monopolization
- Helps cooperative multitasking
Example
package main
import (
"fmt"
"runtime"
)
func main() {
go func() {
fmt.Println("Goroutine")
}()
runtime.Gosched()
fmt.Println("Main")
}
Interview Insight
Gosched() pauses the current goroutine temporarily without blocking the thread.
** 13. What Is a Ticker in Go?
A ticker repeatedly sends values to a channel at fixed intervals.
It is provided by the time package.
Example
ticker := time.NewTicker(time.Second)
Receiving Tick Events
for t := range ticker.C {
fmt.Println(t)
}
Stopping Ticker
ticker.Stop()
Common Use Cases
- Periodic tasks
- Monitoring systems
- Scheduled background jobs
Interview Insight
Difference:
-
Timer→ executes once -
Ticker→ executes repeatedly
* 14. What Is a select Block Without a default Case?
A select block without a default case blocks until one channel operation becomes ready.
Example
select {
case msg := <-ch:
fmt.Println(msg)
}
Behavior
- Waits indefinitely for channel activity
- Synchronizes goroutines naturally
Important Point
If all channels are blocked:
-
selectalso blocks
Interview Insight
Adding a default case makes select non-blocking.
*** 15. How Does Garbage Collection Work in Go?
Go uses an automatic garbage collector (GC) to free unused memory.
The GC identifies objects that are no longer reachable and removes them.
Go GC Features
- Automatic memory management
- Concurrent garbage collection
- Low pause times
GC Phases
| Phase | Purpose |
|---|---|
| Mark | Identify live objects |
| Sweep | Remove unused objects |
Benefits
- Prevents manual memory management
- Reduces memory-related bugs
- Improves developer productivity
Interview Insight
Go uses:
Concurrent Mark-and-Sweep Garbage Collection
*** 16. What Is a Memory Leak?
A memory leak occurs when memory is allocated but never released or reused properly.
In Go, leaks usually happen because objects remain referenced and cannot be garbage collected.
Common Causes
- Goroutine leaks
- Unclosed channels
- Global references
- Large unused slices
Example
var data [][]byte
If unused data remains referenced, memory usage grows continuously.
Interview Insight
Go has garbage collection, but memory leaks are still possible.
*** 17. How Do You Prevent Memory Leaks?
Common Prevention Techniques
1. Close Resources Properly
defer file.Close()
2. Avoid Goroutine Leaks
Use:
- Context cancellation
- Proper channel handling
3. Remove Unused References
Allow garbage collector to reclaim memory.
4. Limit Slice Retention
Avoid holding large underlying arrays unnecessarily.
5. Use Profiling Tools
Go provides:
pprof
Interview Insight
Most Go memory leaks are caused by:
- Long-lived references
- Stuck goroutines
*** 18. How Do You Optimize Go Code Performance?
Common Optimization Strategies
1. Reduce Memory Allocations
Reuse objects when possible.
2. Use Buffered Channels
Minimize blocking overhead.
3. Avoid Unnecessary Goroutines
Too many goroutines increase scheduling cost.
4. Use Efficient Data Structures
Choose proper:
- Maps
- Slices
- Struct layouts
5. Benchmark Code
go test -bench=.
6. Profile Applications
Use:
pprof
7. Minimize Reflection
Reflection is slower than direct access.
Interview Insight
Performance optimization should always be:
- Measured
- Benchmarked
- Profile-driven
*** 19. Strategies to Reduce GC Overhead
Effective Strategies
1. Reduce Object Allocations
Fewer allocations reduce GC workload.
2. Reuse Objects
Use:
sync.Pool
3. Avoid Temporary Objects
Minimize short-lived allocations.
4. Use Value Types Carefully
Large heap allocations increase GC pressure.
5. Preallocate Slices
make([]int, 0, 1000)
6. Reduce Pointer Usage
More pointers increase GC scanning work.
Interview Insight
GC performance becomes critical in:
- High-throughput systems
- Low-latency applications
*** 20. Performance Implications of Large Structs
Large structs can negatively impact performance because copying them requires more memory and CPU time.
Problems with Large Structs
- Higher memory usage
- Slower copying
- Increased cache misses
- More GC pressure
Example
type LargeStruct struct {
Data [10000]int
}
Passing this by value is expensive.
Better Approach
Use pointer receivers.
Example
func process(ls *LargeStruct) {
}
Optimization Tips
- Use pointer receivers
- Avoid unnecessary copies
- Keep structs compact
Interview Insight
Struct field ordering can improve memory alignment and cache efficiency.
21. Difference Between Stack and Heap Allocation in Go
Stack Allocation
- Memory is allocated inside the function call stack.
- Allocation and deallocation are very fast.
- Memory is automatically freed when the function returns.
- Used for small, short-lived variables.
Example
func add() int {
x := 10
y := 20
return x + y
}
Here, x and y are usually stored on the stack.
Heap Allocation
- Memory is allocated in the heap area.
- Managed by Go Garbage Collector (GC).
- Slightly slower because GC needs to track it.
- Used when data must survive outside the function scope.
Example
func createUser() *User {
u := User{Name: "Go"}
return &u
}
u escapes the function, so it is allocated on the heap.
Key Differences
| Feature | Stack | Heap |
|---|---|---|
| Speed | Faster | Slower |
| Managed By | Compiler | Garbage Collector |
| Lifetime | Function scope | Until no references remain |
| Allocation Cost | Cheap | Expensive |
| GC Impact | No | Yes |
Interview Insight
Interviewers often ask:
- “How does Go decide stack vs heap?”
- “What causes heap allocation?”
- “How does heap allocation affect performance?”
The answer is: Go uses escape analysis.
22. What is Escape Analysis?
Definition
Escape analysis is a compiler optimization technique used by Go to determine whether a variable should be allocated on the stack or heap.
If a variable “escapes” the function scope, Go allocates it on the heap.
Example
func test() *int {
x := 10
return &x
}
Here, x escapes because its address is returned.
So Go allocates x on the heap.
Non-Escaping Example
func test() int {
x := 10
return x
}
x does not escape, so it stays on the stack.
Check Escape Analysis
Use:
go build -gcflags="-m"
Example output:
moved to heap: x
Why It Matters
- Heap allocations increase GC pressure.
- More heap usage can reduce performance.
- Reducing escaping variables improves efficiency.
Interview Tip
A common interview question:
“How can you reduce heap allocations in Go?”
Answer:
- Avoid returning pointers unnecessarily.
- Use value types when possible.
- Minimize closures capturing variables.
- Reuse objects using sync.Pool.
23. How Do You Optimize JSON Encoding/Decoding?
Common Optimization Techniques
1. Use Structs Instead of map[string]interface{}
Bad
var data map[string]interface{}
json.Unmarshal(b, &data)
Better
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
Structs are faster and type-safe.
2. Use jsoniter for High Performance
Standard library is good, but faster alternatives exist.
Example:
import jsoniter "github.com/json-iterator/go"
var json = jsoniter.ConfigCompatibleWithStandardLibrary
3. Reuse Buffers
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(data)
Avoid repeated allocations.
4. Avoid Reflection Heavy Operations
Reflection is expensive.
Prefer:
- concrete structs
- typed fields
- known schemas
5. Use Streaming for Large JSON
decoder := json.NewDecoder(file)
Avoid loading huge JSON into memory.
6. Use omitempty
Name string `json:"name,omitempty"`
Reduces payload size.
Interview Insight
Interviewers may ask:
- Why is JSON slow?
- Why is reflection expensive?
- Difference between Marshal and Encoder?
Important point:
Go’s standard JSON package uses reflection internally.
24. What is the unsafe Package?
Definition
The unsafe package allows low-level memory manipulation in Go.
It bypasses Go’s type safety.
Common Uses
- Pointer conversion
- Memory optimization
- Interacting with C libraries
- High-performance systems programming
Example
package main
import (
"fmt"
"unsafe"
)
func main() {
x := 10
p := unsafe.Pointer(&x)
fmt.Println(*(*int)(p))
}
Important Components
| Function | Purpose |
|---|---|
| unsafe.Pointer | Generic pointer type |
| unsafe.Sizeof() | Size of variable |
| unsafe.Alignof() | Memory alignment |
| unsafe.Offsetof() | Field offset |
Risks
- Breaks type safety
- Can cause memory corruption
- May break across Go versions
- Harder to maintain
Interview Insight
Important statement:
“Use unsafe only when absolutely necessary.”
Mostly used in:
- runtime
- serialization libraries
- high-performance systems
25. Difference Between map and struct
Struct
- Fixed fields
- Compile-time type safety
- Faster access
- Better memory efficiency
Example
type User struct {
Name string
Age int
}
Map
- Dynamic key-value storage
- Flexible structure
- Slower than struct
- Uses hashing internally
Example
m := map[string]int{
"age": 25,
}
Key Differences
| Feature | Struct | Map |
|---|---|---|
| Schema | Fixed | Dynamic |
| Performance | Faster | Slower |
| Type Safety | Strong | Weak |
| Memory Usage | Lower | Higher |
| Access Method | Direct | Hash lookup |
When to Use
Use Struct When:
- Schema is known
- Performance matters
- Strong typing needed
Use Map When:
- Dynamic fields required
- Unknown keys
- Flexible JSON-like data
Interview Insight
Common question:
“Why are structs generally faster than maps?”
Answer:
Struct field access is direct memory access.
Map access requires hashing and bucket lookup.
26. What is a Nil Interface and Why is it Problematic?
Important Concept
An interface is nil only when:
- type = nil
- value = nil
Problem Example
type User struct{}
func main() {
var u *User = nil
var i interface{} = u
fmt.Println(i == nil)
}
Output:
false
Why?
Because interface internally stores:
(type, value)
In this case:
(*User, nil)
Type exists, so interface is not nil.
Internal Representation
interface = {
type
data
}
Both must be nil for interface to be nil.
Why It Causes Bugs
This commonly causes:
- incorrect nil checks
- panic situations
- hidden runtime bugs
Proper Check
if i == nil {
// true only if both type and value nil
}
Or use reflection carefully when needed.
Interview Insight
This is one of the most commonly asked tricky Go interview questions.
Interviewers check whether candidates understand:
- interface internals
- runtime representation
- typed nil behavior
27. Difference Between Value Receiver and Pointer Receiver
Value Receiver
Behavior
- Receives a copy of the struct.
- Original object is not modified.
Example
type User struct {
Name string
}
func (u User) change() {
u.Name = "Go"
}
Pointer Receiver
Behavior
- Receives pointer to original struct.
- Can modify original data.
Example
func (u *User) change() {
u.Name = "Go"
}
Key Differences
| Feature | Value Receiver | Pointer Receiver |
|---|---|---|
| Copies Object | Yes | No |
| Modify Original | No | Yes |
| Memory Efficient | Less | More |
| Large Struct Performance | Worse | Better |
When to Use Pointer Receiver
Use pointer receiver when:
- modifying struct
- large struct
- avoiding copies
- consistency required
Interview Insight
A very common question:
“Can methods with pointer receivers satisfy interfaces?”
Yes.
But method sets matter.
Rule
- Value can call both receiver types.
- Pointer can call both receiver types.
- Interface satisfaction depends on method set.
28. What is Deep Copy vs Shallow Copy in Go?
Shallow Copy
Definition
Copies only top-level structure.
Nested references still point to same memory.
Example
type User struct {
Scores []int
}
u1 := User{
Scores: []int{1,2,3},
}
u2 := u1
u2.Scores[0] = 100
Both objects change because slice references same array.
Deep Copy
Definition
Copies all nested data into new memory.
Objects become completely independent.
Example
u2 := User{
Scores: make([]int, len(u1.Scores)),
}
copy(u2.Scores, u1.Scores)
Now modifying u2 does not affect u1.
Key Differences
| Feature | Shallow Copy | Deep Copy |
|---|---|---|
| Nested References Shared | Yes | No |
| Independent Objects | No | Yes |
| Faster | Yes | No |
| Memory Usage | Lower | Higher |
Interview Insight
Go assignments are usually shallow copies.
Special care required for:
- slices
- maps
- pointers
- channels
29. Difference Between new and make
new
Purpose
Allocates memory and returns pointer.
Works with all types.
Example
p := new(int)
Equivalent:
var x int
p := &x
make
Purpose
Initializes built-in reference types.
Used only for:
- slice
- map
- channel
Example
m := make(map[string]int)
s := make([]int, 5)
c := make(chan int)
Key Differences
| Feature | new | make |
|---|---|---|
| Returns | Pointer | Initialized object |
| Initializes | No | Yes |
| Used For | Any type | slice/map/channel |
Important Interview Point
Wrong
m := new(map[string]int)
Map is still nil internally.
Correct
m := make(map[string]int)
Interview Insight
make is special because slices, maps, and channels require runtime initialization.
30. How Do Slices Internally Work?
Slice Structure
A slice internally contains:
pointer -> underlying array
length
capacity
Internal Representation
slice = {
ptr
len
cap
}
Example
s := []int{1,2,3}
Internally:
ptr -> array
len = 3
cap = 3
Append Behavior
Example
s := []int{1,2,3}
s = append(s, 4)
If capacity is full:
- New larger array allocated
- Old elements copied
- New element added
Important Concept
Slices are views over arrays.
Multiple slices can share same underlying array.
Example
a := []int{1,2,3,4}
b := a[:2]
c := a[:2]
b[0] = 100
fmt.Println(c[0])
Output:
100
Because both share same array.
Length vs Capacity
s := make([]int, 3, 5)
len = 3
cap = 5
Interview Insight
Very common questions:
- How does append work internally?
- When does reallocation happen?
- Why do slices sometimes affect each other?
- Difference between length and capacity?
31. How Are Maps Internally Implemented in Go?
Definition
Go maps are implemented as hash tables.
They provide:
- fast lookup
- insertion
- deletion
Average complexity:
O(1)
Internal Structure
A Go map internally contains:
- buckets
- hash function
- overflow buckets
How It Works
Step 1: Hashing
When you insert a key:
m["name"] = "Go"
Go:
- Computes hash of
"name" - Finds bucket using hash
- Stores key-value pair inside bucket
Buckets
Each bucket stores:
- multiple key-value pairs
- usually up to 8 entries
If bucket becomes full:
- overflow bucket is created
Internal Representation
map
├── bucket array
│ ├── bucket 1
│ ├── bucket 2
│ └── ...
Collision Handling
Two keys may generate same bucket.
This is called:
hash collision
Go handles collisions using:
- overflow buckets
Map Growth
When map becomes large:
- Go allocates new buckets
- entries are gradually moved
This is called:
incremental resizing
Important Properties
| Feature | Details |
|---|---|
| Ordered? | No |
| Thread Safe? | No |
| Lookup Speed | Average O(1) |
| Uses Hashing | Yes |
Why Maps Are Not Thread-Safe
Concurrent writes can corrupt internal buckets.
Example:
fatal error: concurrent map writes
Use:
- mutex
- sync.Map
for concurrent access.
Interview Insight
Very common interview questions:
- Why are maps fast?
- How are collisions handled?
- Why are maps not thread-safe?
- What happens during resizing?
32. What is Shadowing in Go?
Definition
Shadowing happens when a variable declared in an inner scope has the same name as a variable in an outer scope.
The inner variable hides the outer variable.
Example
x := 10
if true {
x := 20
fmt.Println(x)
}
fmt.Println(x)
Output:
20
10
Inside block:
- new
xis created - outer
xbecomes hidden
Common Real-World Bug
file, err := os.Open("a.txt")
if err != nil {
return err
}
if data, err := io.ReadAll(file); err != nil {
return err
}
Inner err shadows outer err.
This can create debugging confusion.
Why It Is Dangerous
Shadowing may cause:
- hidden bugs
- unexpected values
- logic errors
- difficult debugging
Best Practices
Avoid Reusing Variable Names
Prefer:
data, readErr := io.ReadAll(file)
instead of:
data, err := io.ReadAll(file)
Interview Insight
Interviewers often ask:
“Why is := sometimes dangerous?”
Because it may accidentally create a new variable instead of updating existing one.
33. Best Practices for Error Handling in Go
1. Handle Errors Explicitly
Go prefers explicit error handling.
Example
data, err := readFile()
if err != nil {
return err
}
2. Add Context to Errors
Bad:
return err
Better:
return fmt.Errorf("failed to read file: %w", err)
Adds debugging information.
3. Avoid Ignoring Errors
Bad:
data, _ := readFile()
Ignored errors can cause production failures.
4. Use Sentinel Errors Carefully
Prefer:
- wrapped errors
- custom errors
instead of many global variables.
5. Keep Errors Simple
Good error messages:
failed to connect database
invalid user id
Avoid:
Error occurred!!!
6. Don’t Use panic for Normal Errors
Use panic only for:
- unrecoverable situations
- programmer mistakes
7. Use errors.Is and errors.As
Modern Go error handling:
if errors.Is(err, os.ErrNotExist) {
}
8. Return Early
Prefer:
if err != nil {
return err
}
instead of nested logic.
Interview Insight
Go interviewers strongly evaluate:
- clean error handling
- readable code
- proper wrapping
- debugging clarity
34. How Do You Design Custom Error Types?
Why Custom Errors?
Custom errors allow:
- additional context
- structured error handling
- type checking
Basic Custom Error
Example
type ValidationError struct {
Field string
}
func (e ValidationError) Error() string {
return "invalid field: " + e.Field
}
Usage
func validate(age int) error {
if age < 18 {
return ValidationError{
Field: "age",
}
}
return nil
}
Type Assertion
err := validate(10)
if v, ok := err.(ValidationError); ok {
fmt.Println(v.Field)
}
Modern Approach Using errors.As
var v ValidationError
if errors.As(err, &v) {
fmt.Println(v.Field)
}
Best Practices
Include Useful Context
Good:
type APIError struct {
StatusCode int
Message string
}
Keep Error Messages Human Readable
Example:
user not found
payment failed
Support Wrapping
func (e APIError) Error() string {
return e.Message
}
Interview Insight
Interviewers often ask:
- Why use custom errors?
- Difference between sentinel and custom errors?
- Why use errors.As?
35. What is Deferred Execution?
Definition
defer delays execution of a function until the surrounding function returns.
Example
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
Output:
hello
world
Common Uses
1. Closing Files
file, _ := os.Open("a.txt")
defer file.Close()
2. Unlocking Mutexes
mu.Lock()
defer mu.Unlock()
3. Cleanup Operations
defer cleanup()
Important Property
Deferred calls execute in:
LIFO order
(last in, first out)
Example
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
Output:
3
2
1
Interview Insight
Very common interview questions:
- When does defer execute?
- In what order do deferred calls run?
- Does defer affect performance?
36. How Does defer Work Internally?
Internal Mechanism
When Go sees:
defer f()
It:
- Stores deferred call in stack-like structure
- Executes deferred calls before function exits
Important Detail
Arguments are evaluated immediately.
Example
x := 10
defer fmt.Println(x)
x = 20
Output:
10
Because argument captured at defer time.
LIFO Execution
Deferred functions run in reverse order.
Named Return Value Example
func test() (x int) {
defer func() {
x++
}()
return 5
}
Output:
6
Deferred function can modify named return values.
Performance Consideration
Older Go versions:
- defer was expensive
Modern Go:
- optimized significantly
Still avoid defer inside extremely hot loops when performance critical.
Interview Insight
Interviewers frequently ask:
- Are defer arguments evaluated immediately?
- Can defer modify return values?
- Why is defer useful?
37. When Should You Use panic?
Definition
panic stops normal execution of the current goroutine.
Used for:
- unrecoverable situations
- programmer errors
Example
panic("something went wrong")
Appropriate Use Cases
1. Impossible States
default:
panic("unexpected state")
2. Initialization Failures
if db == nil {
panic("database init failed")
}
3. Programmer Mistakes
Example:
- invalid assumptions
- corrupt internal state
When NOT to Use panic
Do NOT use panic for:
- normal business errors
- validation failures
- user input errors
Instead:
return error
panic vs error
| Feature | panic | error |
|---|---|---|
| Recoverable | Usually No | Yes |
| Usage | Critical failures | Normal failures |
| Stops Flow | Yes | No |
Interview Insight
Important statement:
“panic is exceptional, not normal control flow.”
38. How Do You Recover from panic in Goroutines?
Important Rule
recover() only works inside a deferred function.
Example
func worker() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
}
}()
panic("boom")
}
Why Goroutines Matter
A panic inside goroutine can crash the entire program if not recovered.
Safe Goroutine Pattern
go func() {
defer func() {
if r := recover(); r != nil {
log.Println("panic recovered:", r)
}
}()
process()
}()
Important Interview Point
Recovery only works in:
- same goroutine
You cannot recover panic from another goroutine.
Common Production Use Cases
- worker pools
- HTTP servers
- background jobs
Interview Insight
Frequently asked:
“Can main goroutine recover panic from child goroutine?”
Answer:
No
Each goroutine needs its own recovery mechanism.
39. What is Sentinel Error in Go?
Definition
A sentinel error is a predefined reusable error value.
Usually declared as global variable.
Example
var ErrNotFound = errors.New("not found")
Usage
if err == ErrNotFound {
}
Modern Preferred Approach
Use:
errors.Is(err, ErrNotFound)
instead of direct comparison.
Standard Library Examples
io.EOF
os.ErrNotExist
context.Canceled
Advantages
- reusable
- easy comparisons
- standardized errors
Disadvantages
- tight coupling
- global dependency
- less flexible
Best Practice
Use sentinel errors only when:
- callers must identify specific error
Interview Insight
Common interview question:
“Why is errors.Is preferred over == ?”
Because wrapped errors may not match direct equality.
40. What is Error Wrapping?
Definition
Error wrapping means adding extra context while preserving the original error.
Introduced officially in:
Go 1.13
Example
if err != nil {
return fmt.Errorf("failed to read config: %w", err)
}
%w wraps original error.
Benefits
- preserves original error
- adds debugging context
- supports error chains
Checking Wrapped Errors
errors.Is
if errors.Is(err, os.ErrNotExist) {
}
Extracting Error Types
errors.As
var pathErr *os.PathError
if errors.As(err, &pathErr) {
}
Error Chain Example
API Error
└── DB Error
└── Network Error
Why Wrapping Is Important
Without wrapping:
- original cause gets lost
With wrapping:
- debugging becomes easier
Interview Insight
Most commonly asked points:
- Difference between
%vand%w - Why use errors.Is?
- What is error chain?
- How does errors.As work?