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ì
newhoặcmakemộ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.Poolcho việc này: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ụ:
Cách tối ưu:
Dùng
strings.Builderhoặcbytes.Bufferthay vì cộng chuỗi hoặcfmt.Sprintfnhiề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:
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.Errorffor better error messages.Structured logging using libraries like
zaporlogrushelps capture detailed logs with fields for easier searching and analysis.Use
deferto close resources andrecoverin 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 Acần gọi hàm trongpackage B, và ngược lạipackage Bcũ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ảAvàBđều importCthay vì import nhau.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.
Giờ
userkhông cần importemail, chỉ biếtNotifier.mainsẽ wire chúng lại:
→ Đâ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):
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.
8 .What is a nil channel?
nil channel?A nil channel blocks both sending and receiving operations. But becareful, It might cause deadlock.
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