Golang
1. What is a buffered channel?
A buffered channel has a specified capacity and allows sending of values until the buffer is full. It doesn't require a receiver to be ready to receive.
main(){
ch1 := make(chan int,1)
ch1<-1 //this will not block, thanks to buffer
ch2 := make(chan int)
ch2 <- 1 // this will block main because no other goroutine will read it
}
2. What is an interface in Go and how to implement it?
An interface in Go is a type that specifies a set of method signatures. It allows polymorphism by defining behavior.
A type implements an interface by implementing all its methods:
type Animal interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
3. What are the performance implications of using large struct types as function parameters in Go? How would you optimize it?
Passing large structs by value results in a copy being created, which can be expensive in terms of memory and performance. To optimize, pass pointers to structs (*Struct)
instead of copying the entire struct. This avoids duplicating data and reduces the overhead of copying large amounts of memory.
4. What is interface{} in Go, and how does it relate to empty interfaces?
interface{}
is an empty interface, meaning it can hold values of any type since all types implement zero methods. It is often used for generic data structures or when working with functions that can accept any type. Type assertions (value.(type))
and type switches allow you to determine the dynamic type of a value stored in an interface{}
.
Using object pooling to reuse memory.
Reducing the allocation of short-lived objects.
Preallocating slices or structs to avoid frequent allocations.
Để giảm GC overhead (chi phí mà Garbage Collector phải bỏ ra để dọn dẹp bộ nhớ), ba chiến lược bạn nêu thực ra là các best practice thường dùng trong Golang hoặc các ngôn ngữ có GC khác.
Object Pooling (tái sử dụng bộ nhớ thay vì cấp phát mới)
Ý tưởng: thay vì mỗi lần cần object thì
new
hoặcmake
một cái mới (rồi để GC thu hồi), ta tái sử dụng object cũ đã được cấp phát.Go cung cấp sẵn
sync.Pool
cho việc này:var bufPool = sync.Pool{ New: func() any { return make([]byte, 1024) // pre-allocate 1KB buffer }, } func handler() { buf := bufPool.Get().([]byte) defer bufPool.Put(buf) // use buf... }
Lợi ích: giảm số lần cấp phát heap → giảm tần suất GC phải quét.
Lưu ý: phù hợp cho object ngắn hạn, tái sử dụng nhiều (ví dụ buffer, struct request), nhưng không phải lúc nào cũng có lợi (pool sai có thể làm tốn RAM hơn).
Giảm allocation của short-lived objects
Trong Go, các object sống ngắn (tạo ra trong scope nhỏ rồi chết ngay) thường được cấp phát trên heap (GC phải thu gom) thay vì stack (compiler không escape được).
Ví dụ:
// Tạo nhiều string tạm thời trong loop -> nhiều allocation for i := 0; i < 1000; i++ { s := fmt.Sprintf("value-%d", i) process(s) }
Cách tối ưu:
Dùng
strings.Builder
hoặcbytes.Buffer
thay vì cộng chuỗi hoặcfmt.Sprintf
nhiều lần.Hạn chế tạo slice/map tạm thời trong loop.
Viết code để compiler escape analysis có thể giữ dữ liệu trên stack thay vì heap.
Preallocating slices/structs
Nếu biết trước kích thước dữ liệu, nên preallocate thay vì để slice tự grow:
// Sai: mỗi lần append có thể reallocate + copy arr := []int{} for i := 0; i < 1000; i++ { arr = append(arr, i) } // Đúng: cấp phát đủ capacity từ đầu arr := make([]int, 0, 1000) for i := 0; i < 1000; i++ { arr = append(arr, i) }
Tương tự cho
map
: nếu biết trước khoảng số phần tử → dùngmake(map[int]int, expectedSize)
.Lợi ích: giảm số lần reallocate, copy, giảm pressure cho GC.
5. How would you handle error handling and logging in a large Go project?
For error handling, use custom error types to add context to errors, and wrap errors using
fmt.Errorf
for better error messages.Structured logging using libraries like
zap
orlogrus
helps capture detailed logs with fields for easier searching and analysis.Use
defer
to close resources andrecover
in critical sections to handle unexpected panics gracefully.Centralize error handling logic for common operations like database queries to ensure consistency.
6. How do you manage circular dependencies in Go packages?
Refactor common functionality into a separate package to avoid mutual dependency.
Use interfaces to decouple dependencies, allowing one package to depend only on the interface rather than the implementation.
Reorganize code to reduce the number of dependencies, ensuring that the dependency graph remains acyclic.
1. Refactor common functionality vào package riêng
Vấn đề thường gặp:
package A
cần gọi hàm trongpackage B
, và ngược lạipackage B
cũng gọi lạipackage A
→ thành vòng lặp.Cách xử lý: tách phần code dùng chung ra
package C
để cảA
vàB
đều importC
thay vì import nhau.// package util (tách riêng ra) package util func FormatName(s string) string { ... } // package user import "myapp/util" func CreateUser(name string) { util.FormatName(name) } // package order import "myapp/util" func CreateOrder(customer string) { util.FormatName(customer) }
Lợi ích: dependency graph rõ ràng, không bị vòng.
2. Dùng interfaces để decouple
Thay vì package A gọi trực tiếp struct cụ thể trong package B → hãy định nghĩa interface ở package A (consumer) và package B chỉ cần implement.
// package user package user type Notifier interface { Send(msg string) error } type Service struct { notifier Notifier } func (s *Service) Signup() { s.notifier.Send("Welcome new user") } // package email package email type EmailService struct {} func (e *EmailService) Send(msg string) error { ... }
Giờ
user
không cần importemail
, chỉ biếtNotifier
.main
sẽ wire chúng lại:notifier := &email.EmailService{} userSvc := user.Service{notifier: notifier}
→ Đây chính là inversion of dependency, giữ cho dependency graph 1 chiều.
3. Reorganize code để dependency graph luôn acyclic
Khi design, hãy nhớ Go compiler enforce acyclic import graph → nếu vòng thì sẽ fail ngay.
Nguyên tắc tổ chức package:
Theo layer: domain → usecase/service → delivery (http/grpc) → main (composition root).
Package dưới (domain, entity) không được import package trên (API, service).
Ví dụ trong Clean Architecture (DDD):
/domain # entities, interfaces (no deps) /repository # implement domain.Repository /service # business logic, depends on domain /delivery # HTTP/GRPC handlers, depend on service /cmd # main.go, wires all together
Như vậy không bao giờ có vòng.
7. What is a pointer in Go, how to declare it?
A pointer holds the memory address of a value. It is used to pass references instead of copying values.
x := 5
var p *int
p = &x // & sign generates a pointer to x, assign it to p
*p // get the value that pointer p pointing to
8 .What is a nil
channel?
nil
channel?A nil
channel blocks both sending and receiving operations. But becareful, It might cause deadlock
.
main() {
var ch chan int // nil channel
select {
case ch <- 1: // This will block because ch is nil
fmt.Println("Sent to channel")
case <-time.After(1 * time.Second): // This ensures that after 1 second, it times out
fmt.Println("Timed out")
}
}
9. What is the init
function?
init
function?init
is a special function that initializes package-level variables. It is executed before main
.
10. Can you have multiple init
functions?
init
functions?Yes, but they will be executed in the order they appear.
Last updated