Backend Interview Questions - Easy
Easy-level backend interview questions covering HTTP, REST APIs, databases, and server fundamentals.
Q1: What is REST and what are its principles?
Answer:
REST Principles
1. Stateless: Each request contains all information needed 2. Client-Server: Separation of concerns 3. Cacheable: Responses can be cached 4. Uniform Interface: Standard HTTP methods 5. Layered System: Client doesn't know if connected to end server 6. Code on Demand (optional): Server can send executable code
HTTP Methods (CRUD)
Example REST API:
1GET /api/users # List all users
2GET /api/users/123 # Get user by ID
3POST /api/users # Create new user
4PUT /api/users/123 # Update user (full)
5PATCH /api/users/123 # Update user (partial)
6DELETE /api/users/123 # Delete user
Q2: What are HTTP status codes and their meanings?
Answer:
Common Status Codes
2xx Success:
200 OK: Request succeeded201 Created: Resource created204 No Content: Success, no body
3xx Redirection:
301 Moved Permanently: Resource moved302 Found: Temporary redirect304 Not Modified: Use cached version
4xx Client Error:
400 Bad Request: Invalid request401 Unauthorized: Authentication required403 Forbidden: No permission404 Not Found: Resource doesn't exist429 Too Many Requests: Rate limited
5xx Server Error:
500 Internal Server Error: Server error502 Bad Gateway: Invalid response from upstream503 Service Unavailable: Server overloaded504 Gateway Timeout: Upstream timeout
Q3: What is the difference between SQL and NoSQL databases?
Answer:
SQL Example (PostgreSQL)
1-- Create table
2CREATE TABLE users (
3 id SERIAL PRIMARY KEY,
4 name VARCHAR(100) NOT NULL,
5 email VARCHAR(100) UNIQUE NOT NULL,
6 created_at TIMESTAMP DEFAULT NOW()
7);
8
9-- Insert
10INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
11
12-- Query
13SELECT * FROM users WHERE email = 'alice@example.com';
14
15-- Join
16SELECT orders.*, users.name
17FROM orders
18JOIN users ON orders.user_id = users.id;
NoSQL Example (MongoDB)
1// Insert document
2db.users.insertOne({
3 name: "Alice",
4 email: "alice@example.com",
5 created_at: new Date(),
6 preferences: {
7 theme: "dark",
8 notifications: true
9 },
10 tags: ["premium", "verified"]
11});
12
13// Query
14db.users.findOne({ email: "alice@example.com" });
15
16// Update
17db.users.updateOne(
18 { email: "alice@example.com" },
19 { $set: { "preferences.theme": "light" } }
20);
When to Use Each
Q4: What is database indexing and why is it important?
Answer:
Without Index
With Index
Creating Indexes
1-- Create index
2CREATE INDEX idx_users_email ON users(email);
3
4-- Composite index
5CREATE INDEX idx_users_name_email ON users(name, email);
6
7-- Unique index
8CREATE UNIQUE INDEX idx_users_email_unique ON users(email);
9
10-- Check query plan
11EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';
Trade-offs:
- ✅ Faster reads
- ❌ Slower writes (index must be updated)
- ❌ More storage space
Q5: What is authentication vs authorization?
Answer:
Authentication Flow
Authorization Flow
Implementation Example
1package main
2
3import (
4 "context"
5 "encoding/json"
6 "net/http"
7 "strings"
8
9 "github.com/golang-jwt/jwt/v5"
10)
11
12type contextKey string
13
14const userContextKey contextKey = "user"
15
16// User represents authenticated user
17type User struct {
18 ID string `json:"id"`
19 Email string `json:"email"`
20 Role string `json:"role"`
21}
22
23// AuthMiddleware validates JWT token
24func AuthMiddleware(secretKey string) func(http.Handler) http.Handler {
25 return func(next http.Handler) http.Handler {
26 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27 authHeader := r.Header.Get("Authorization")
28 if authHeader == "" {
29 http.Error(w, `{"error":"No token provided"}`, http.StatusUnauthorized)
30 return
31 }
32
33 parts := strings.Split(authHeader, " ")
34 if len(parts) != 2 || parts[0] != "Bearer" {
35 http.Error(w, `{"error":"Invalid authorization header"}`, http.StatusUnauthorized)
36 return
37 }
38
39 token, err := jwt.Parse(parts[1], func(token *jwt.Token) (interface{}, error) {
40 return []byte(secretKey), nil
41 })
42
43 if err != nil || !token.Valid {
44 http.Error(w, `{"error":"Invalid token"}`, http.StatusUnauthorized)
45 return
46 }
47
48 claims, ok := token.Claims.(jwt.MapClaims)
49 if !ok {
50 http.Error(w, `{"error":"Invalid token claims"}`, http.StatusUnauthorized)
51 return
52 }
53
54 user := &User{
55 ID: claims["id"].(string),
56 Email: claims["email"].(string),
57 Role: claims["role"].(string),
58 }
59
60 ctx := context.WithValue(r.Context(), userContextKey, user)
61 next.ServeHTTP(w, r.WithContext(ctx))
62 })
63 }
64}
65
66// AuthorizeMiddleware checks user roles
67func AuthorizeMiddleware(allowedRoles ...string) func(http.Handler) http.Handler {
68 return func(next http.Handler) http.Handler {
69 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
70 user, ok := r.Context().Value(userContextKey).(*User)
71 if !ok {
72 http.Error(w, `{"error":"Not authenticated"}`, http.StatusUnauthorized)
73 return
74 }
75
76 allowed := false
77 for _, role := range allowedRoles {
78 if user.Role == role {
79 allowed = true
80 break
81 }
82 }
83
84 if !allowed {
85 http.Error(w, `{"error":"Insufficient permissions"}`, http.StatusForbidden)
86 return
87 }
88
89 next.ServeHTTP(w, r)
90 })
91 }
92}
93
94// Usage
95func main() {
96 mux := http.NewServeMux()
97
98 // Protected admin endpoint
99 adminHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
100 json.NewEncoder(w).Encode(map[string]string{"message": "Admin access granted"})
101 })
102
103 mux.Handle("/admin/users",
104 AuthMiddleware("your-secret-key")(
105 AuthorizeMiddleware("admin")(adminHandler),
106 ),
107 )
108
109 http.ListenAndServe(":8080", mux)
110}
Q6: What is JWT (JSON Web Token)?
Answer:
JWT Structure
1eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
2
3[Header].[Payload].[Signature]
Header:
1{
2 "alg": "HS256",
3 "typ": "JWT"
4}
Payload:
1{
2 "sub": "1234567890",
3 "name": "John Doe",
4 "iat": 1516239022,
5 "exp": 1516242622
6}
Signature:
1HMACSHA256(
2 base64UrlEncode(header) + "." + base64UrlEncode(payload),
3 secret
4)
Creating and Verifying JWT
1package main
2
3import (
4 "errors"
5 "time"
6
7 "github.com/golang-jwt/jwt/v5"
8)
9
10type Claims struct {
11 UserID string `json:"userId"`
12 Email string `json:"email"`
13 Role string `json:"role"`
14 jwt.RegisteredClaims
15}
16
17// CreateToken generates a JWT token
18func CreateToken(user User, secretKey string) (string, error) {
19 claims := Claims{
20 UserID: user.ID,
21 Email: user.Email,
22 Role: user.Role,
23 RegisteredClaims: jwt.RegisteredClaims{
24 ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
25 IssuedAt: jwt.NewNumericDate(time.Now()),
26 },
27 }
28
29 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
30 return token.SignedString([]byte(secretKey))
31}
32
33// VerifyToken validates and decodes a JWT token
34func VerifyToken(tokenString, secretKey string) (*Claims, error) {
35 token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
36 return []byte(secretKey), nil
37 })
38
39 if err != nil {
40 return nil, err
41 }
42
43 if !token.Valid {
44 return nil, errors.New("invalid token")
45 }
46
47 claims, ok := token.Claims.(*Claims)
48 if !ok {
49 return nil, errors.New("invalid token claims")
50 }
51
52 return claims, nil
53}
54
55// Login handler example
56func LoginHandler(w http.ResponseWriter, r *http.Request) {
57 // Authenticate user (check credentials)
58 user := User{
59 ID: "123",
60 Email: "user@example.com",
61 Role: "admin",
62 }
63
64 token, err := CreateToken(user, "your-secret-key")
65 if err != nil {
66 http.Error(w, "Failed to create token", http.StatusInternalServerError)
67 return
68 }
69
70 json.NewEncoder(w).Encode(map[string]string{
71 "token": token,
72 })
73}
Q7: What is CORS and why is it needed?
Answer:
Same-Origin Policy
CORS Flow
Implementing CORS
1package main
2
3import (
4 "net/http"
5 "strings"
6)
7
8// CORSMiddleware handles CORS headers
9func CORSMiddleware(allowedOrigins []string) func(http.Handler) http.Handler {
10 return func(next http.Handler) http.Handler {
11 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
12 origin := r.Header.Get("Origin")
13
14 // Check if origin is allowed
15 allowed := false
16 for _, allowedOrigin := range allowedOrigins {
17 if allowedOrigin == "*" || allowedOrigin == origin {
18 allowed = true
19 break
20 }
21 }
22
23 if allowed {
24 w.Header().Set("Access-Control-Allow-Origin", origin)
25 w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
26 w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
27 w.Header().Set("Access-Control-Allow-Credentials", "true")
28 }
29
30 // Handle preflight requests
31 if r.Method == http.MethodOptions {
32 w.WriteHeader(http.StatusOK)
33 return
34 }
35
36 next.ServeHTTP(w, r)
37 })
38 }
39}
40
41// Usage
42func main() {
43 mux := http.NewServeMux()
44
45 // Your handlers
46 mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
47 json.NewEncoder(w).Encode(map[string]string{"message": "Users endpoint"})
48 })
49
50 // Wrap with CORS middleware
51 handler := CORSMiddleware([]string{"https://example.com"})(mux)
52
53 http.ListenAndServe(":8080", handler)
54}
55
56// Simple CORS middleware (allow all)
57func SimpleCORSMiddleware(next http.Handler) http.Handler {
58 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
59 w.Header().Set("Access-Control-Allow-Origin", "*")
60 w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
61 w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
62
63 if r.Method == http.MethodOptions {
64 w.WriteHeader(http.StatusOK)
65 return
66 }
67
68 next.ServeHTTP(w, r)
69 })
70}
Q8: What is the difference between GET and POST?
Answer:
GET Request
1GET /api/users?page=1&limit=10 HTTP/1.1
2Host: api.example.com
3Authorization: Bearer <token>
1package main
2
3import (
4 "encoding/json"
5 "net/http"
6 "strconv"
7)
8
9// GET handler
10func GetUsersHandler(w http.ResponseWriter, r *http.Request) {
11 // Parse query parameters
12 pageStr := r.URL.Query().Get("page")
13 limitStr := r.URL.Query().Get("limit")
14
15 page, _ := strconv.Atoi(pageStr)
16 limit, _ := strconv.Atoi(limitStr)
17
18 if page == 0 {
19 page = 1
20 }
21 if limit == 0 {
22 limit = 10
23 }
24
25 // Fetch users from database
26 users := fetchUsers(page, limit)
27
28 w.Header().Set("Content-Type", "application/json")
29 json.NewEncoder(w).Encode(map[string]interface{}{
30 "users": users,
31 "page": page,
32 "limit": limit,
33 })
34}
POST Request
1POST /api/users HTTP/1.1
2Host: api.example.com
3Content-Type: application/json
4Authorization: Bearer <token>
5
6{
7 "name": "Alice",
8 "email": "alice@example.com"
9}
1package main
2
3import (
4 "encoding/json"
5 "net/http"
6)
7
8type CreateUserRequest struct {
9 Name string `json:"name"`
10 Email string `json:"email"`
11}
12
13type UserResponse struct {
14 ID string `json:"id"`
15 Name string `json:"name"`
16 Email string `json:"email"`
17}
18
19// POST handler
20func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
21 var req CreateUserRequest
22
23 // Decode JSON body
24 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
25 http.Error(w, "Invalid request body", http.StatusBadRequest)
26 return
27 }
28
29 // Validate
30 if req.Name == "" || req.Email == "" {
31 http.Error(w, "Name and email are required", http.StatusBadRequest)
32 return
33 }
34
35 // Create user in database
36 user := createUser(req.Name, req.Email)
37
38 w.Header().Set("Content-Type", "application/json")
39 w.WriteStatus(http.StatusCreated)
40 json.NewEncoder(w).Encode(UserResponse{
41 ID: user.ID,
42 Name: user.Name,
43 Email: user.Email,
44 })
45}
Comparison
| Feature | GET | POST |
|---|---|---|
| Purpose | Retrieve | Submit |
| Data location | URL | Body |
| Cacheable | Yes | No |
| Idempotent | Yes | No |
| Size limit | Yes (~2KB) | No |
| Visible in URL | Yes | No |
| Bookmarkable | Yes | No |
Q9: What is database normalization?
Answer:
Unnormalized Data
1-- ❌ Redundant data
2CREATE TABLE orders (
3 order_id INT,
4 customer_name VARCHAR(100),
5 customer_email VARCHAR(100),
6 customer_address TEXT,
7 product_name VARCHAR(100),
8 product_price DECIMAL,
9 quantity INT
10);
11
12-- Problems:
13-- 1. Customer data repeated for each order
14-- 2. Product data repeated for each order
15-- 3. Update anomalies (change email in multiple places)
Normalized Data (3NF)
1-- ✅ Separate tables, no redundancy
2
3CREATE TABLE customers (
4 customer_id SERIAL PRIMARY KEY,
5 name VARCHAR(100),
6 email VARCHAR(100) UNIQUE,
7 address TEXT
8);
9
10CREATE TABLE products (
11 product_id SERIAL PRIMARY KEY,
12 name VARCHAR(100),
13 price DECIMAL
14);
15
16CREATE TABLE orders (
17 order_id SERIAL PRIMARY KEY,
18 customer_id INT REFERENCES customers(customer_id),
19 order_date TIMESTAMP DEFAULT NOW()
20);
21
22CREATE TABLE order_items (
23 order_item_id SERIAL PRIMARY KEY,
24 order_id INT REFERENCES orders(order_id),
25 product_id INT REFERENCES products(product_id),
26 quantity INT,
27 price_at_purchase DECIMAL
28);
Normal Forms
1NF (First Normal Form):
- Atomic values (no lists in cells)
- No repeating groups
2NF (Second Normal Form):
- 1NF + No partial dependencies
- Non-key attributes depend on entire primary key
3NF (Third Normal Form):
- 2NF + No transitive dependencies
- Non-key attributes depend only on primary key
Q10: What is caching and common caching strategies?
Answer:
Cache Layers
Caching Strategies
1. Cache-Aside (Lazy Loading):
1package main
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "time"
8
9 "github.com/redis/go-redis/v9"
10)
11
12type User struct {
13 ID string `json:"id"`
14 Name string `json:"name"`
15 Email string `json:"email"`
16}
17
18func GetUser(ctx context.Context, rdb *redis.Client, userID string) (*User, error) {
19 // Check cache first
20 cacheKey := fmt.Sprintf("user:%s", userID)
21 cached, err := rdb.Get(ctx, cacheKey).Result()
22
23 if err == nil {
24 // Cache hit
25 var user User
26 if err := json.Unmarshal([]byte(cached), &user); err == nil {
27 return &user, nil
28 }
29 }
30
31 // Cache miss: fetch from database
32 user, err := fetchUserFromDB(userID)
33 if err != nil {
34 return nil, err
35 }
36
37 // Store in cache
38 userData, _ := json.Marshal(user)
39 rdb.Set(ctx, cacheKey, userData, time.Hour)
40
41 return user, nil
42}
2. Write-Through:
1func UpdateUser(ctx context.Context, rdb *redis.Client, userID string, data User) (*User, error) {
2 // Update database
3 user, err := updateUserInDB(userID, data)
4 if err != nil {
5 return nil, err
6 }
7
8 // Update cache
9 cacheKey := fmt.Sprintf("user:%s", userID)
10 userData, _ := json.Marshal(user)
11 rdb.Set(ctx, cacheKey, userData, time.Hour)
12
13 return user, nil
14}
3. Write-Behind (Write-Back):
1func UpdateUserAsync(ctx context.Context, rdb *redis.Client, queue Queue, userID string, data User) error {
2 // Update cache immediately
3 cacheKey := fmt.Sprintf("user:%s", userID)
4 userData, _ := json.Marshal(data)
5 if err := rdb.Set(ctx, cacheKey, userData, time.Hour).Err(); err != nil {
6 return err
7 }
8
9 // Queue database update (async)
10 return queue.Enqueue("update-user", map[string]interface{}{
11 "userId": userID,
12 "data": data,
13 })
14}
Cache Invalidation
1package main
2
3import (
4 "context"
5 "time"
6
7 "github.com/redis/go-redis/v9"
8)
9
10func CacheInvalidationExamples(ctx context.Context, rdb *redis.Client) {
11 // TTL (Time To Live)
12 rdb.Set(ctx, "key", "value", time.Hour) // Expires in 1 hour
13
14 // Manual invalidation
15 rdb.Del(ctx, "user:123")
16
17 // Pattern-based invalidation
18 keys, _ := rdb.Keys(ctx, "user:*").Result()
19 if len(keys) > 0 {
20 rdb.Del(ctx, keys...)
21 }
22
23 // Or use SCAN for large datasets (more efficient)
24 iter := rdb.Scan(ctx, 0, "user:*", 0).Iterator()
25 for iter.Next(ctx) {
26 rdb.Del(ctx, iter.Val())
27 }
28}
Summary
Key backend concepts:
- REST: Stateless, uniform interface, HTTP methods
- HTTP Status Codes: 2xx success, 4xx client error, 5xx server error
- SQL vs NoSQL: Relational vs document, ACID vs BASE
- Indexing: B-tree, faster queries, trade-offs
- Authentication vs Authorization: Identity vs permissions
- JWT: Token-based auth, stateless
- CORS: Cross-origin security, browser protection
- GET vs POST: Retrieve vs submit, idempotent vs not
- Normalization: Reduce redundancy, normal forms
- Caching: Strategies, invalidation, layers
These fundamentals are essential for backend development.
Related Snippets
- Backend Interview Questions - Hard
Hard-level backend interview questions covering distributed systems, advanced … - Backend Interview Questions - Medium
Medium-level backend interview questions covering microservices, advanced …