API Gateway Project - Interview Questions &
Answers
This document contains a comprehensive list of potential interview questions
and detailed answers based on your Go API Gateway project. These questions
cover architecture, concurrency, networking, algorithm implementation, and Go-
specific patterns.
1. Architecture & Design Patterns
Q1: Can you describe the overall architecture of your API Gateway?
Answer: My API Gateway is a production-grade distributed HTTP reverse
proxy written in Go. Its core components include: * Reverse Proxy En-
gine: Built on top of [Link], it forwards incoming client re-
quests to backend services. * Pluggable Load Balancer: An interface-driven
([Link]) routing layer supporting Round Robin, Least Connections,
Weighted Round Robin, and Consistent Hashing. * Middleware Chain: A
decorator pattern pipeline handling cross-cutting concerns like Request ID gen-
eration, Structured Logging, Token Bucket Rate Limiting, and JWT Authenti-
cation. * Health Checker: A background goroutine that periodically pings
backend services to monitor liveness, automatically removing dead nodes from
the load-balancing pool. * Dynamic Configuration & Admin API: Allows
adding/removing backends dynamically at runtime and updating configuration
without restarting the server via an [Link] protected state.
Q2: Why did you choose Go for this project?
Answer: Go is arguably the best language for network programming and build-
ing gateways due to: * Built-in Concurrency: Goroutines and channels make
handling thousands of concurrent HTTP requests extremely efficient with min-
imal memory overhead compared to OS threads. * Standard Library: Go’s
net/http and net/http/httputil standard libraries are incredibly robust and
production-ready, allowing me to build a reverse proxy without heavy third-
party frameworks. * Performance: It compiles to statically linked machine
code, offering high performance, low latency, and predictable garbage collec-
tion pauses, which is critical for a component sitting in the critical path of all
network traffic.
Q3: How did you implement the Middleware pattern in Go?
Answer: I used the classic Go middleware pattern, which is a function that
takes an [Link] and returns a new [Link] (type Middleware
func([Link]) [Link]). I implemented a Chain function that
1
iterates over a slice of middlewares in reverse order, wrapping the base han-
dler. For example, in the logging middleware, I wrap the [Link]
with a custom responseRecorder to capture the HTTP status code and mea-
sure the duration using [Link](), then log the request details and record
Prometheus metrics.
2. Concurrency & Thread Safety
Q4: The Gateway handles many concurrent requests. How did you
ensure thread safety when reading/writing backend states or config-
uration?
Answer: Thread safety is crucial in a gateway. I used two main approaches de-
pending on the read/write frequency: 1. [Link]: Used in the Gateway
struct for the routing state. Since reading the current configuration (picking
a backend) happens on every single request (highly concurrent), but updating
the configuration (adding/removing backends) is rare, RWMutex allows multiple
goroutines to acquire a read lock simultaneously (RLock). A write lock (Lock)
is only acquired during dynamic configuration changes, ensuring no reads hap-
pen during state mutation. 2. sync/atomic Package: For high-frequency
counters where a Mutex would cause lock contention, I used atomic opera-
tions. For example, CurrentConns in the Least Connections load balancer uses
atomic.AddInt64 and atomic.LoadInt64. The backend liveness status (Alive)
uses [Link] to allow the health checker to update the status while the
proxy reads it lock-free.
Q5: In your Rate Limiter, you use a [Link] per Token Bucket.
Could this become a bottleneck?
Answer: To prevent a global bottleneck, I implemented a decentralized
locking strategy. Instead of one global mutex for all rate limiting, I use a
map[string]*tokenBucket where the key is the client IP. * The Manager has a
mutex to protect the map itself (when adding a new IP). * Each tokenBucket
has its own [Link]. This means requests from different IPs do not block
each other at the token bucket level, drastically reducing lock contention.
3. Load Balancing Algorithms
Q6: Can you explain how you implemented the Least Connections
algorithm? How does it avoid race conditions?
Answer: The Least Connections balancer iterates through all alive
backends and selects the one with the lowest CurrentConns value.
To avoid race conditions: * I wrap the reverse proxy execution with
2
[Link](be) before forwarding the request and defer
[Link](be) when it completes. * These functions use
atomic.AddInt64(&[Link], 1) and -1. * When iterating, the
picker uses atomic.LoadInt64(&[Link]) to safely read the connec-
tion count. This guarantees that concurrent requests accurately increment and
read the counters without standard race conditions.
Q7: How does your Weighted Round Robin algorithm work under
the hood?
Answer: I implemented Weighted Round Robin using a Greatest Common Di-
visor (GCD) based approach, inspired by LVS (Linux Virtual Server). 1. On
initialization, I calculate the maximum weight among all backends and the GCD
of all weights. 2. I maintain a current weight threshold. I cycle through the
backends. 3. If a backend’s weight is greater than or equal to the current
threshold, it gets selected. 4. Once all backends have been checked for the
current threshold, I decrease current by the GCD. If current hits zero, I
reset it to the maximum weight. This algorithm efficiently distributes requests
proportional to the weights without generating large arrays of duplicated back-
end references.
Q8: What is Consistent Hashing, why is it useful, and how did you
build it?
Answer: Consistent Hashing ensures that requests from the same client (e.g.,
same IP) are consistently routed to the same backend server. This is highly
useful for sticky sessions or maximizing backend cache hit rates. Implemen-
tation: * I used Go’s hash/fnv (Fowler-Noll-Vo) non-cryptographic hash func-
tion because it’s extremely fast and provides a good distribution. * I extract
the client IP (checking X-Forwarded-For if behind another proxy, falling back
to RemoteAddr). * I hash the IP string, compute hash % len(backends), and
pick that index. If the selected backend is dead, I linearly probe to the next
alive backend (idx + i) % len(backends).
4. Rate Limiting
Q9: Can you explain the Token Bucket algorithm and how you im-
plemented it in your rate limiter?
Answer: The Token Bucket algorithm regulates the rate of requests while
allowing for short bursts. * Concept: A bucket holds a maximum number of
“tokens” (burst capacity). Tokens are added to the bucket at a fixed rate (e.g.,
10 tokens/sec). Each request consumes one token. If the bucket is empty, the
request is rejected (HTTP 429). * My Implementation: Instead of running
a background goroutine to constantly add tokens (which is CPU intensive), I
3
calculate tokens lazily on every request. * When Allow() is called, I calculate
the time elapsed since the lastCheck. I multiply the elapsed time (in seconds)
by the rate to find out how many new tokens should be added. I cap the tokens
at the burst limit, subtract 1 token for the current request, and allow it. This
is highly efficient and precise.
5. System Resiliency & Observability
Q10: How does your gateway handle backend failures automatically?
Answer: I implemented an active [Link]. * It runs a background
goroutine with a [Link] based on the configured interval. * On every
tick, it concurrently (using goroutines) sends an HTTP GET request to the
configured HealthURL of every backend. * If the request times out or returns a
non-200 status code, it sets the backend’s [Link] Alive flag to false. *
The load balancer pickers (Next() function) always check if [Link]()
before returning a backend to the proxy. Dead backends are instantly skipped,
providing seamless failover.
Q11: How is the system observable?
Answer: Observability is implemented through three pillars: 1. Structured
Logging: Using a custom logger (wrapping log or a structured package),
emitting key-value pairs (Method, Path, Status, Duration, IP). This is
injected via middleware. 2. Request Tracing: A middleware generates
a unique Request ID (UUID) for every incoming request, stores it in the
[Link] ([Link]), and injects it into both the logs and
the HTTP Response Headers (X-Request-ID). 3. Metrics: I integrated a
Prometheus-compatible /metrics endpoint. The metrics middleware records
http_requests_total (Counter) segmented by status code and method, and
http_request_duration_seconds (Summary/Histogram) to track latency
percentiles.
6. Dynamic Configuration
Q12: How does the Admin API modify the routing without restarting
the server?
Answer: The Gateway struct holds a slice of backends and a picker interface.
When a POST request is made to /admin/backends/add: 1. The gateway ac-
quires a Write Lock ([Link]()) to pause new read requests momentarily. 2.
It parses the new URL and creates a new [Link] instance. 3. It ap-
pends it to the internal [Link] slice. 4. Crucially, it rebuilds the picker
(e.g., [Link]([Link])) and replaces the old picker with
4
the new one. 5. The lock is released. Subsequent requests immediately use the
new picker instance with zero downtime.