Skip to main content

Kotlin engineer system design interview questions

9 questions on system design for kotlin engineer candidates. Expect prompts such as “Design a distributed rate limiter for a Ktor API” and “Design an offline-first Android app with sync”, each with a worked answer outline and the follow-ups interviewers push on.

Questions

Showing 1 to 9 of 9 system design questions.

As asked

Design a rate limiter for a Ktor REST API that enforces per-user request limits of 100 requests per minute across multiple server instances. Walk me through the data store choice, the algorithm, and how you integrate it as a Ktor plugin.

Sample answer outline

A strong answer picks Redis with a sliding window counter or token bucket. Redis INCR + EXPIRE implements fixed window; a sorted set with ZADD/ZRANGEBYSCORE implements sliding window. The Ktor plugin is installed via createApplicationPlugin and intercepts the ApplicationCallPipeline.Plugins phase, checking the counter before proceeding. The candidate should discuss atomic Redis operations (EVAL with Lua) to avoid race conditions and the latency cost of a Redis round-trip per request.

Expect these follow-ups

  • How do you handle Redis being unavailable, fail-open or fail-closed?
  • How would you make the rate limiter configurable per route?
system-designrate-limitingktorredisbackend

As asked

Design an offline-first Android app for a task management tool. Users create, update, and delete tasks offline. Changes sync to a backend when connectivity returns. Walk me through your local storage strategy, conflict resolution approach, and how Kotlin coroutines and WorkManager fit in.

Sample answer outline

Room is the local source of truth. Each entity has a syncStatus field (PENDING, SYNCED, DELETED). WorkManager enqueues a periodic sync with a network constraint, runs a coroutine via CoroutineWorker, and uses the Room Flow to detect pending changes. Conflict resolution can use last-write-wins with server timestamps or a vector clock for stricter guarantees. A strong answer mentions idempotent server mutations and exponential backoff for failed sync.

Expect these follow-ups

  • How do you handle a case where the server deletes a task that the user has locally modified while offline?
  • How would you test the sync logic without a real backend?
system-designoffline-firstandroidroomworkmanager

As asked

You need to build a Kotlin microservice that consumes Kafka events, enriches them by calling an external HTTP API, and produces enriched events to another Kafka topic. Design the architecture, explain your concurrency model, and identify failure modes.

Sample answer outline

The solution uses a Kafka consumer loop in a coroutine, dispatching each event to a CoroutineScope for enrichment via Ktor Client's HTTP calls (fully async). The producer sends enriched events back to Kafka. Failure modes include HTTP timeout (retry with backoff inside the coroutine), Kafka broker unavailability (suspend and retry), and poison pill events (dead letter queue). The candidate should discuss consumer group management and offset commit timing (commit after successful produce).

Expect these follow-ups

  • How do you ensure exactly-once processing across the consume-enrich-produce pipeline?
  • How do you scale this horizontally without double-processing?
kafkamicroserviceskotlincoroutinesevent-driven

As asked

You are greenfielding a large Android app with 20 engineers. How would you structure it as a multi-module Gradle project? Explain the module boundaries, how you manage shared dependencies, and how Kotlin Coroutines and Hilt DI interact across module boundaries.

Sample answer outline

The typical structure separates app, feature:*, core:*, and data:* modules. Feature modules depend on core and data but not on each other. Navigation between features uses the Navigation component with deep links or a shared navigator interface. Hilt InstallIn(SingletonComponent) in the app module provides dependencies to feature modules via @EntryPoint. Coroutine scopes from Hilt-injected ViewModels don't cross module boundaries; repositories in data modules use their own dispatcher injection.

Expect these follow-ups

  • How do you prevent circular dependencies between feature modules?
  • What is the build time impact of many modules and how do you mitigate it?
androidmulti-modulearchitecturehiltkotlin

As asked

Design a real-time notification system where users receive push notifications via WebSocket. The system must handle 100,000 concurrent connections across multiple Ktor server instances. Walk me through connection management, fan-out, and graceful degradation.

Sample answer outline

Each Ktor server instance manages WebSocket connections in a ConcurrentHashMap keyed by userId. Fan-out from a backend event uses a pub-sub mechanism like Redis Pub/Sub or Kafka: each server subscribes to a topic and delivers to its connected users. For 100k connections with Ktor on the JVM, the candidate should consider connection backpressure, heartbeat and ping-pong frames, and fallback to Server-Sent Events or long polling for clients that lose the WebSocket.

Expect these follow-ups

  • How do you handle a connection drop and ensure the user doesn't miss notifications?
  • How would you authenticate the WebSocket handshake?
websocketktorrealtimesystem-designscalability

As asked

Design a caching layer for a Ktor backend that serves product catalog data. The catalog changes infrequently but must reflect updates within 60 seconds. Walk me through cache-aside vs write-through, your TTL strategy, and how you handle cache stampede under traffic spikes.

Sample answer outline

Cache-aside reads from Redis and falls back to the database; write-through updates both on every write. For 60-second freshness, a 55-second TTL with stale-while-revalidate handles the gap. Cache stampede is mitigated by probabilistic early recomputation (PER algorithm) or a single-flight mutex that allows only one coroutine to recompute while others wait. The candidate should mention Redis key naming conventions and serialization format (JSON vs Protobuf).

Expect these follow-ups

  • How do you handle cache invalidation when a product update arrives asynchronously via Kafka?
  • How do you test that your caching logic doesn't serve stale data after a TTL expiry?
cachingredisktorsystem-designbackend

As asked

Design a gRPC service in Kotlin that streams live stock price updates to clients. Cover proto definition, Kotlin coroutine-based streaming using grpc-kotlin, back-pressure, and how you handle slow consumers.

Sample answer outline

The proto defines a server-streaming RPC: rpc StreamPrices (PriceRequest) returns (stream PriceUpdate). The grpc-kotlin server implementation is a suspend function receiving a Flow and emitting via send. Back-pressure in grpc-kotlin is handled by the coroutine suspension model: if the client is slow, the emit suspends. Slow consumers are handled by setting a deadline on the server stream or using a conflated channel to drop stale prices for lagging clients.

Expect these follow-ups

  • How do you handle reconnection on the client side without missing updates?
  • How does grpc-kotlin compare to reactive streams (Reactor) for gRPC streaming?
grpckotlinstreamingsystem-designbackend

As asked

You choose Koin for dependency injection in a Kotlin backend. How do you organize Koin modules across a multi-module Gradle project, and what problems arise when two modules define beans for the same type? How do you test components that rely on Koin?

Sample answer outline

Each Gradle module defines its own Koin module object and exports it. The application module aggregates them with startKoin { modules(listOf(...)) }. Conflicts for the same type are resolved via named qualifiers. For testing, Koin provides a testing DSL: declare() overrides specific beans with mocks inside a test scope, avoiding a full container restart. The candidate should compare this to Hilt's compile-time verification advantage.

Expect these follow-ups

  • How would you migrate from Koin to Hilt in an existing Android codebase?
  • What is the overhead of Koin's reflection-based DI compared to Hilt's code-generation approach?
koindependency-injectionkotlinarchitecturetesting

As asked

You are building a public Kotlin SDK for a payment API. The SDK must work on Kotlin/JVM and Kotlin Multiplatform. Walk me through the public API design decisions: how do you expose suspend functions vs callbacks, how do you handle errors, and how do you version the SDK without breaking consumers.

Sample answer outline

Expose suspend functions as the primary API for Kotlin consumers and generate callback overloads for Java using @JvmOverloads or a separate Java-friendly wrapper. Error handling uses a sealed Result type or throws typed exceptions. For KMP, abstract HTTP behind an expect/actual interface with platform HTTP engines (OkHttp for JVM, URLSession for iOS). Versioning uses semantic versioning with a deprecation cycle: @Deprecated with a ReplaceWith before removing APIs.

Expect these follow-ups

  • How do you handle breaking changes in the API model, for example a field being renamed?
  • How do you expose blocking variants of suspend functions for Java callers?
sdk-designkotlin-multiplatformapi-designsystem-designkotlin