ICICI Lombard: Senior Backend Engineer (Golang)
Domain: Motor Division (Insurance)
Experience: 5+ Years
Core Tech Stack: Golang, PHP, Laravel, React, MySQL
Job Description Breakdown & Strategy
Based on the provided Job Description, here are the key themes the interviewers will test you on and how to prepare for them:
- Software Modularity & Architecture:
- JD Expectation: Deep understanding of making software modular, writing the whole backend code, and building reusable libraries.
- Preparation: Brush up on Clean Architecture, Domain-Driven Design (DDD), and SOLID principles in Go. Be ready to explain how you organize Go packages (e.g., standard layout vs domain-driven) and avoid circular dependencies.
- API Integration (Revamped Blaze & IL Systems):
- JD Expectation: API integration with "revamped blaze" and other ICICI Lombard legacy/new systems.
- Preparation: Expect questions on RESTful API design, GRPC, handling timeouts, circuit breakers, rate limiting, and integrating with third-party SOAP/XML or REST APIs reliably.
- Optimization (Speed & Scalability):
- JD Expectation: Optimization for maximum speed and scalability.
- Preparation: Go concurrency (Goroutines, Channels, WaitGroups), profiling using `pprof`, optimizing database queries, adding Redis caching, and horizontal scaling strategies.
- Database Design (MySQL):
- JD Expectation: Creating schemas that support business processes, MySQL proficiency.
- Preparation: ACID properties, transaction isolation levels, indexing strategies, handling database migrations, and avoiding N+1 queries. Given the insurance domain, expect questions on data integrity and handling financial transactions safely.
- Security, Auth & Compliance:
- JD Expectation: User auth between multiple systems, security/data protection, accessibility/compliance.
- Preparation: OAuth2.0, JWT, SSO (Single Sign-On). Discuss encrypting PII/PHI (Personally Identifiable Information — critical for insurance), SQL injection prevention, and CORS.
- Test Driven Development (TDD):
- JD Expectation: Implementing TDD.
- Preparation: Know how to write tests in Go (`go test`, `testing` package), use of `testify`, table-driven tests, and how to mock dependencies (using interfaces and `gomock`).
- Multi-Stack Awareness (PHP/Laravel/React):
- JD Expectation: Integration with front-end, tech stack includes PHP/Laravel/React.
- Preparation: Since you are applying for Golang, you might be migrating older PHP/Laravel services to Go, or building APIs for a React frontend. Be ready to discuss the pros/cons of Go vs PHP, and how to design APIs that are friendly for front-end consumption (GraphQL, BFF pattern, or well-structured REST).
Top Interview Questions & Answers
1. How do you design a modular, scalable architecture in Golang?
Answer:
In Go, modularity is achieved through proper package management and the use of interfaces.
- Clean Architecture: I separate the application into layers: Handlers (HTTP/gRPC), Use Cases (Business Logic), and Repositories (Data Access).
- Dependency Injection: Rather than tightly coupling components, I define interfaces for repositories and third-party services, and inject them into the Use Cases. This makes the code highly modular and testable.
// Example of Dependency Injection type PolicyRepository interface { Save(ctx context.Context, policy Policy) error } type PolicyService struct { repo PolicyRepository } func NewPolicyService(r PolicyRepository) *PolicyService { return &PolicyService{repo: r} } - Package Layout: I group files by domain (e.g.,
/motor,/claims,/policies) rather than by type (e.g., all controllers in one folder). This follows Domain-Driven Design (DDD) principles.
2. You need to integrate our new Go backend with "Blaze" and several legacy ICICI Lombard systems. How do you ensure reliability?
Answer:
When integrating with external or legacy systems, I assume they can fail or be slow.
- Timeouts: Use Go's
context.WithTimeoutfor every outgoing HTTP/gRPC request to prevent our goroutines from hanging indefinitely.// Example: 5-second timeout for external API call ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() req, _ := http.NewRequestWithContext(ctx, "GET", "https://blaze.api/legacy", nil) resp, err := http.DefaultClient.Do(req) - Circuit Breaker: Implement a circuit breaker (like Netflix Hystrix or
go-resiliency) to stop sending requests if the legacy system is down, allowing it time to recover and serving a fallback response to the user. - Retries with Exponential Backoff: For transient errors (e.g., HTTP 503), implement retries with increasing delays.
- Idempotency: Ensure the APIs we call (or expose) are idempotent, especially for financial transactions like premium payments, so retrying a request doesn't result in double-charging.
3. We use MySQL heavily. How would you design a schema for a Motor Insurance Policy and ensure fast reads?
Answer:
- Schema Design: I would normalize the data into tables like
Users,Vehicles,Policies, andPayments. APolicytable would have foreign keys toUserandVehicle. - Indexing: Add B-Tree indexes on frequently queried columns, such as
user_id,vehicle_registration_number, andpolicy_status. - Read Optimization: For heavy read operations (like fetching policy details for a dashboard), I would introduce Redis as a caching layer (Cache-Aside pattern). If a user views their policy, we check Redis first; on a miss, we query MySQL and store the result in Redis with a TTL.
- Transactions: For issuing a policy, we must write to
PoliciesandPaymentssimultaneously. I would wrap these in a MySQL transaction withREAD COMMITTEDorREPEATABLE READisolation to ensure ACID compliance.
4. Explain how you implement Test-Driven Development (TDD) in Golang.
Answer:
TDD involves writing the test before the actual implementation (Red-Green-Refactor).
- Interfaces for Mocking: I define interfaces for any external dependency (e.g.,
Databaseinterface,EmailSenderinterface). - Table-Driven Tests: Go's idioms heavily favor table-driven tests. I create a slice of anonymous structs defining the input, expected output, and expected error, and iterate through them using
t.Run().func TestCalculatePremium(t *testing.T) { tests := []struct { name string age int expected int }{ {"Young driver", 20, 1500}, {"Experienced driver", 35, 800}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := CalculatePremium(tt.age) assert.Equal(t, tt.expected, result) }) } } - Mock Generation: I use
gomockor manually write mock structs that implement the interfaces to isolate the business logic during testing. - Coverage: I run
go test -coverto ensure the critical business paths are covered, particularly for complex logic like insurance premium calculations.
5. How do you handle User Authentication and Authorization between multiple systems?
Answer:
Since the JD mentions multiple systems (Blaze, legacy IL systems, React frontend), a centralized auth mechanism is required.
- Single Sign-On (SSO): I would use OAuth 2.0 or OIDC (OpenID Connect). The user authenticates once via an Identity Provider (IdP).
- JWT (JSON Web Tokens): The IdP issues a JWT. The React frontend sends this JWT in the
Authorization: Bearerheader to the Go backend. - Stateless Verification: The Go backend can verify the JWT signature using the IdP's public key without needing to do a database lookup for every request.
- Role-Based Access Control (RBAC): The JWT payload can include claims like
role: adminorrole: user. In Go, I would write a middleware function that checks the JWT validity and ensures the user has the required role to access the specific API route.
6. How would you optimize a Go application for maximum speed?
Answer:
- Concurrency: Utilize Goroutines to process independent tasks in parallel (e.g., fetching user profile and policy documents simultaneously). Use
sync.WaitGroupto synchronize them.var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() fetchUserProfile() }() go func() { defer wg.Done() fetchPolicyDocuments() }() wg.Wait() // Wait for both concurrent tasks to finish - Connection Pooling: Ensure the database connection pool (
sql.DB) is properly configured (SetMaxOpenConns,SetMaxIdleConns) to prevent exhausting database connections or spending too much time establishing new ones. - Profiling: Use Go's built-in
pproftool to identify CPU bottlenecks and memory leaks (e.g., identifying goroutine leaks or excessive memory allocations). - Avoid Reflection: Reflection in Go (like using the
encoding/jsonpackage extensively on dynamic payloads) is slow. Where possible, use code generation (likeeasyjson) or avoidinterfacetypes.
7. What do you think about the Go vs PHP/Laravel aspect of our stack?
Answer:
(This is a cultural/architectural question based on their stack).
Both have their places. PHP/Laravel is excellent for rapid application development, monolithic structures, and server-side rendering. However, as an application scales, Go shines due to its static typing, incredibly low memory footprint, and native concurrency model.
In a microservices architecture, it makes sense to use Go for high-throughput, compute-intensive APIs (like processing thousands of motor insurance claims or premium calculations concurrently), while PHP might still serve the legacy admin portals. Communication between them can happen via REST APIs or message queues like Kafka/RabbitMQ.
8. How do Goroutines differ from OS Threads, and how do you prevent Race Conditions?
Answer:
- Goroutines vs Threads: Goroutines are user-space threads managed by the Go runtime, not the OS. They are extremely lightweight (starting at ~2KB of stack space compared to 1-2MB for OS threads), meaning you can spawn hundreds of thousands of them. They are multiplexed onto a smaller number of OS threads by the Go scheduler (M:N scheduling).
- Preventing Race Conditions: A race condition occurs when multiple goroutines access the same memory concurrently and at least one is writing. To prevent this, I use the
sync.Mutex(orsync.RWMutexfor read-heavy workloads) to lock critical sections. Alternatively, following Go's proverb: "Do not communicate by sharing memory; instead, share memory by communicating", I use channels to safely pass data between goroutines without explicit locking. I also religiously use the-raceflag (go test -raceorgo build -race) during development to catch issues early.type SafeCounter struct { mu sync.Mutex v map[string]int } func (c *SafeCounter) Inc(key string) { c.mu.Lock() // Lock so only one goroutine at a time can access the map c.v. c.v[key]++ c.mu.Unlock() }
9. In a microservices architecture, how do you handle distributed transactions (e.g., deducting a balance in one service and issuing a policy in another)?
Answer:
Since ACID transactions don't span across multiple microservices databases, we have to rely on Eventual Consistency.
- Saga Pattern: I would implement the Saga pattern. It's a sequence of local transactions. Each local transaction updates the database and publishes an event or message to trigger the next local transaction in the saga.
- Compensating Transactions: If a step fails (e.g., the payment succeeds but the policy issuance fails), the saga executes a series of compensating transactions that undo the changes made by the preceding local transactions (e.g., issuing a refund).
- Implementation: This can be orchestrated via a central controller (Orchestration-based Saga) or through decentralized events published to a message broker like Kafka or RabbitMQ (Choreography-based Saga).
10. How do you implement data protection and secure APIs handling sensitive insurance data?
Answer:
Insurance deals with PII (Personally Identifiable Information) and financial data, so security is paramount.
- Encryption in Transit & at Rest: All API communication must be over HTTPS/TLS 1.2+. Sensitive database columns (like bank accounts or PAN numbers) should be encrypted at rest using a Key Management Service (AWS KMS or HashiCorp Vault) and AES-256-GCM.
- Data Masking: When returning data to the frontend or writing to logs, sensitive information should be masked (e.g., logging
XXXX-XXXX-XXXX-1234instead of a full credit card number). - Input Validation & Parameterized Queries: Always validate incoming payloads against a strict schema (e.g., using Go's
validatorpackage) and use parameterized queries (viadatabase/sqlor an ORM like GORM) to prevent SQL injection. - Rate Limiting & WAF: Implement IP-based and User-based rate limiting to prevent brute-force attacks and DDoS, alongside a Web Application Firewall.
11. What is your approach to building reusable code and libraries for future use?
Answer:
The JD specifically asks for this. To build reusable libraries in Go:
- Keep it focused (Single Responsibility): A library should do one thing well. For example, creating a centralized
loggerpackage or ametricspackage that standardizes how all IL microservices report data. - Design around Interfaces: Accept interfaces and return structs. This allows consumers of the library to mock the library easily in their own unit tests.
- Versioning and Dependency Management: Extract the reusable code into its own Git repository and tag it using Semantic Versioning (v1.0.0). Other projects can import it via Go Modules. Ensure backward compatibility; if introducing breaking changes, bump the major version (v2).
- Documentation: Write clear GoDoc comments for all exported functions and provide a
README.mdwith usage examples.
12. How do you optimize a MySQL database when queries start becoming slow as data scales?
Answer:
- Execution Plan (EXPLAIN): First, I run
EXPLAINon the slow query to see if it's doing a full table scan or using an index. - Indexing: Add B-Tree indexes to columns used in
WHERE,JOIN, andORDER BYclauses. If querying multiple columns together frequently, create a Composite Index (remembering the left-most prefix rule). - Avoid N+1 Queries: In application code, ensure we aren't running a query inside a loop. Use
IN (...)clauses or JOINs to fetch related data in a single batch. - Archiving / Partitioning: In insurance, old expired policies might not be accessed often. We can partition the table by date or archive old records to a data warehouse to keep the hot table small and fast.
- Read Replicas: Route all
SELECTqueries to read-replicas, freeing up the primary master database forINSERT/UPDATE/DELETEoperations.
13. Our tech stack involves integrating React frontends with server-side logic. How do you handle Cross-Origin Resource Sharing (CORS) and API design for React?
Answer:
- CORS: Since the React app (e.g.,
portal.icicilombard.com) might be hosted on a different domain or port than the Go API (e.g.,api.icicilombard.com), the browser enforces the Same-Origin Policy. I would configure the Go backend's CORS middleware to explicitly allow the React app's origin, specifying allowed methods (GET, POST, PUT) and headers (Authorization, Content-Type). - BFF (Backend-for-Frontend) Pattern: Instead of making the React app aggregate data from 5 different microservices, I would build a Go aggregation layer (BFF) that fetches the data concurrently, formats it exactly how the UI needs it, and returns a single JSON payload. This reduces client-side latency and battery drain on mobile devices.
14. How do you ensure high availability and observability for your Go services?
Answer:
- Statelessness: Design the Go application to be completely stateless. Session data should live in Redis, not in application memory. This allows us to spin up multiple instances of the Go app behind a load balancer without worrying about sticky sessions.
- Health Checks: Expose
/healthzand/readyzendpoints. Kubernetes or the Load Balancer uses these to know if the application is ready to accept traffic or if it needs to be restarted. - Observability:
- Logs: Use structured JSON logging (e.g., using
slogorzap) so logs can be easily parsed by ELK/Datadog. - Metrics: Expose Prometheus metrics (CPU, memory, HTTP request durations, error rates).
- Tracing: Implement Distributed Tracing (OpenTelemetry/Jaeger) by passing a trace ID through the context. When a request hits multiple microservices, the trace ID allows us to visualize the entire path and find the exact bottleneck.
- Logs: Use structured JSON logging (e.g., using
15. What is your strategy for managing code using version control (Git) in a collaborative team?
Answer:
The JD requires "Proficient understanding of code versioning tools."
- Branching Strategy: I typically use Trunk-Based Development or GitHub Flow. Main branch is always deployable. We create short-lived feature branches, open Pull Requests (PRs), and merge back to main quickly.
- Code Reviews & CI/CD: A PR must be reviewed by at least one other engineer. I set up GitHub Actions / GitLab CI to automatically run
go fmt,golangci-lint, andgo teston every push. The PR cannot be merged if the build or tests fail. - Merge vs Rebase: I prefer rebasing my feature branch onto the latest
mainbefore merging to keep a clean, linear commit history without unnecessary merge commits.
Behavioral Tips for this Role
- "Displayed ownership in building end-to-end applications": Be ready to use the STAR method (Situation, Task, Action, Result) to describe a project where you took a feature from gathering requirements to database design, backend implementation, and production deployment.
- Communication Skills: The JD emphasizes that "great software engineers are great writers too." During the interview, explain your technical decisions clearly. Mention your practice of writing detailed API documentation (Swagger/OpenAPI), ADRs (Architecture Decision Records), and clean, readable code.
- Financial/Insurance Domain Awareness: Always emphasize Data Security, Auditability, and Reliability. In insurance, a dropped transaction or a data leak is disastrous. Mentioning things like "Immutable ledgers", "PCI-DSS compliance", and "Audit Logs" will score you massive points.