Skip to main content

Kotlin engineer technical fundamentals interview questions

12 questions on technical fundamentals for kotlin engineer candidates. Expect prompts such as “Explain structured concurrency in Kotlin” and “Handle null safety at a Java boundary”, each with a worked answer outline and the follow-ups interviewers push on.

Questions

Showing 1 to 12 of 12 technical fundamentals questions.

As asked

Explain structured concurrency in Kotlin coroutines. How does it change the way you launch background work in an Android or JVM service?

Sample answer outline

Structured concurrency means child coroutines are tied to an explicit parent scope, so cancellation, failure and lifetime are visible in the call structure. In Android this usually means using lifecycle-aware scopes such as viewModelScope rather than GlobalScope. In backend services it means request-scoped or application-scoped CoroutineScope objects with clear shutdown behaviour. A failed child should either cancel siblings or be isolated with SupervisorJob when that is intentional. Candidates trip up by launching fire-and-forget work that outlives the user action, leaks resources, or silently drops exceptions.

Expect these follow-ups

  • When would you use supervisorScope instead of coroutineScope?
  • How do Dispatchers.IO and Dispatchers.Default differ?
  • What happens to child coroutines when a ViewModel is cleared?
coroutinesandroidconcurrency

As asked

A Kotlin service calls a legacy Java library with weak nullability annotations. How do you keep nulls from leaking into the rest of the codebase?

Sample answer outline

The safest pattern is to isolate the Java dependency behind a Kotlin adapter that translates platform types into explicit nullable or non-null types. Validate inputs and outputs at the boundary, and fail early if the Java contract is violated. Avoid spreading !! through business logic because it turns type safety into runtime surprise. Strong candidates mention JSpecify, Checker Framework or JetBrains annotations where the Java side can be improved. The interviewer wants a boundary design, not just a list of Kotlin null operators.

Reference implementation (kotlin)

Kotlin
class LegacyUserAdapter(private val client: LegacyUserClient) {
    fun findUser(id: String): User? {
        val raw = client.lookup(id) ?: return null
        val email = requireNotNull(raw.email) { "legacy user email missing for id=$id" }
        return User(id = raw.id, email = email.lowercase())
    }
}

Expect these follow-ups

  • When is requireNotNull better than returning null?
  • How do platform types differ from String? and String?
  • What would you enforce in code review around !!?
null-safetyjava-interopboundaries

As asked

What are the most common sources of JVM heap allocation in idiomatic Kotlin code and how do you measure and reduce them? Name at least three Kotlin-specific patterns that cause unexpected allocations.

Sample answer outline

Common sources: lambda objects created per call site (unless inline), boxed primitives (Int vs int in generic contexts), autoboxing in map/forEach on IntArray via standard library interop, data class copy creating new instances unnecessarily, and string concatenation in loops. Tools include async-profiler for allocation profiling and Android Studio Memory Profiler. Fixes include using inline functions, primitive arrays, and mutable builders instead of repeated immutable copies.

Expect these follow-ups

  • How does the JVM JIT compiler mitigate some of these allocations via escape analysis?
  • When should you use IntArray vs List<Int> in a performance-sensitive Kotlin function?
jvmgarbage-collectionperformancekotlinallocation

As asked

Explain JVM type erasure and describe three concrete ways Kotlin works around it or exposes it to the developer. Include both compile-time and runtime perspectives.

Sample answer outline

Type erasure means generic type parameters are not available at runtime; List<String> and List<Int> are the same class at runtime. Kotlin works around this with: (1) reified type parameters on inline functions that substitute the concrete type at the call site; (2) TypeToken patterns for JSON deserialization via Type objects; (3) explicit Class<T> or KClass<T> parameters as a conventional workaround. is checks on generic types require star projection.

Expect these follow-ups

  • Why does Kotlin not allow is List<String> at runtime and what does it suggest instead?
  • How does Gson use TypeToken to recover generic type info at runtime?
type-erasuregenericsjvmkotlinreified

As asked

Describe the full lifecycle of a Kotlin coroutine Job from creation to completion. What are the intermediate states, and what triggers transitions between them? When is a cancelled Job different from a failed Job?

Sample answer outline

States: New (created but not started), Active (running), Completing (waiting for children), Completed (all done), Cancelling (cancel requested but not finished cleanup), and Cancelled. Cancel transitions to Cancelling, which completes cleanup (finally blocks), then Cancelled. A failed Job goes through Completing/Cancelling with the exception stored in the Job, propagating to the parent. A cancelled Job throws CancellationException, which is not a failure for the parent.

Expect these follow-ups

  • How do you check if a Job is cancelled vs failed from the outside?
  • What is the difference between job.cancel() and job.cancelAndJoin()?
coroutinesjob-lifecyclekotlinstructured-concurrencyinternals

As asked

Explain how class delegation works with the by keyword in Kotlin. How does the compiler generate the delegate implementation, and how would you use it to add logging to every call on an existing interface without subclassing?

Sample answer outline

Class delegation generates forwarding methods that call the delegate instance for every interface method. To add logging, create a wrapper class that takes an instance of the interface, delegates all methods via by, and overrides only the ones you want to log. This is the decorator pattern without the boilerplate. The candidate should note that only interface methods are delegated, not class methods, and that delegation cannot be conditional per method without overriding.

Expect these follow-ups

  • Can you delegate to multiple interfaces in the same class?
  • How does this compare to Java's Proxy API for dynamic delegation?
delegationdesign-patternskotlininterfacesdecorator

As asked

When does using Kotlin string templates become a performance concern? What does the compiler generate for a simple template like 'Hello $name' and when should you switch to a StringBuilder explicitly?

Sample answer outline

A simple string template compiles to StringBuilder.append calls for two parts, which is efficient for one-off concatenation. The problem arises in tight loops: each iteration creates a new StringBuilder and result String. An explicit StringBuilder reused across iterations avoids this allocation. The candidate should mention buildString {} as an idiomatic Kotlin wrapper around StringBuilder.

Expect these follow-ups

  • How would you benchmark the difference between string template and buildString in a loop?
  • Does Kotlin's string template support arbitrary expressions inside ${}?
stringsperformancekotlinjvmallocation

As asked

What is continuation-passing style and how does the Kotlin compiler use it to implement suspend functions on the JVM? What does the generated bytecode for a simple suspend function look like conceptually?

Sample answer outline

The compiler transforms each suspend function into a state machine that takes an extra Continuation<T> parameter. Each suspension point becomes a state. On completion or suspension, the function stores local variables in the Continuation and either returns COROUTINE_SUSPENDED or calls resume on the continuation. This avoids allocating a new stack frame per suspension, enabling thousands of coroutines on a handful of OS threads.

Expect these follow-ups

  • Why do suspend functions only appear in the call stack as a single frame in a profiler?
  • What is the BaseContinuationImpl class and what role does it play?
coroutinescontinuationcpskotlinjvm-bytecode

As asked

Kotlin allows operator overloading via the operator keyword. Give two examples where overloading is a natural fit and two examples where it creates confusing code. What guideline would you put in your team's style guide?

Sample answer outline

Good fits: + for combining query builders or appending items to a DSL node, * for scaling a vector. Confusing cases: overloading + to mean HTTP request merge (not additive in the mathematical sense), or using invoke() in a way that hides that a function call is happening. The style guide guideline should say: only override operators that match the mathematical or linguistic intuition of the symbol for the type.

Expect these follow-ups

  • Can you overload the == operator in Kotlin and what does it compile to?
  • What is the invoke operator and when is it appropriate?
operator-overloadingkotlincode-stylelanguage-featuresreadability

As asked

How do you configure detekt in a Kotlin project to enforce team-specific rules, and have you ever written a custom detekt rule? Walk me through the structure of a custom rule that detects GlobalScope usage.

Sample answer outline

Detekt uses a detekt.yml config file and can be added as a Gradle plugin. Custom rules extend Rule, override visitCallExpression, and check if the callee receiver resolves to GlobalScope. The rule is registered in a RuleSetProvider and packaged as a JAR on the detekt extensions classpath. The candidate should mention that detekt integrates with CI via a Gradle task and can fail builds on violations.

Expect these follow-ups

  • How do you suppress a detekt warning for a specific line without disabling the rule project-wide?
  • What is the difference between detekt and ktlint, and do you use both?
detektstatic-analysiskotlintoolingcode-quality

As asked

What is a Kotlin contract and how does it enable the compiler to perform smart casts in situations it otherwise could not? Give a concrete example using callsInPlace or returns.

Sample answer outline

Contracts are hints to the compiler about a function's behavior. returns(true) implies the condition was true after the call, enabling smart casts for null checks in custom validation functions. callsInPlace guarantees a lambda is called exactly once, so variables initialized inside it are treated as definitely assigned by the compiler. callsInPlace and returns contracts were stabilized in Kotlin 1.9 and no longer require an opt-in annotation. More advanced contract effects remain experimental and still need @OptIn(ExperimentalContracts::class). The candidate should distinguish between the stable subset and the experimental parts.

Expect these follow-ups

  • What happens at runtime if a contract is violated?
  • Show me a case where callsInPlace enables a val to be assigned inside a lambda.
contractssmart-castkotlincompileradvanced

As asked

What are Kotlin context receivers (introduced experimentally in 1.6.20) and how do they differ from extension functions? Give a use case where context receivers solve a problem that extension functions cannot elegantly handle.

Sample answer outline

Context receivers (experimental since Kotlin 1.6.20) allow a function to require multiple implicit receivers without the nesting required by extension functions. An extension function has one receiver; context receivers allow declaring several via context(LoggingContext, TransactionContext). A use case is a database transaction that also needs a logging context. The candidate should note that context receivers were replaced by context parameters in Kotlin 2.2, which use a cleaner syntax and stricter scoping rules. Any new code should prefer context parameters, and the original context() syntax should be treated as deprecated.

Expect these follow-ups

  • How do context receivers interact with extension functions in terms of priority?
  • What problems does context receiver solve in a DSL scenario?
context-receiverskotlinlanguage-featuresadvanceddsl