Generics & Variance Interview Questions

KOTLIN › Types & Classes

In plain terms, why does Kotlin let you declare variance at the class declaration (out/in) while Java forces you to specify wildcards at every use site? What's the practical, day-to-day benefit?

What a strong answer covers: Declaration-site variance means the author of a generic type states once, in the class definition, that it only ever produces T and never consumes it, and every caller everywhere automatically gets the covariant subtyping benefit without writing anything extra at each call site, unlike Java where you must remember to add ? extends/? super correctly every time you declare a variable of that generic type. The practical payoff is less boilerplate and fewer mistakes, since the variance rule is enforced once by the type's author instead of trusted to every consumer.

Walk me through a real API design decision: when would you mark a generic interface out T versus leaving it invariant, and what do you give up by doing so?

What a strong answer covers: You mark out T when the interface only returns or produces T and never accepts it as a method parameter, like a Repository that only exposes getAll(): List<T>, which then lets callers substitute a more specific type where a less specific one is expected. What you give up is any method that would take a T as input; the compiler rejects it outright because a producer position and a consumer position can't share a covariant type parameter, forcing either a redesign, a use-site projection, or splitting into separate reader and writer interfaces.

You're building a generic JSON-deserialization helper using reified, similar to Gson's TypeToken workaround. What are you actually gaining, and what are the costs a senior engineer should flag before reaching for it?

What a strong answer covers: The gain is being able to check x is T or reference T::class at runtime inside the function without the caller manually passing a Class token, since inline plus reified lets the compiler substitute the real type at every call site. The costs to flag: the function must be inline, so its body is pasted into every call site and can bloat bytecode if called from many places, it can't be called from Java, and reified type information doesn't propagate through nested generic calls, you can't hand a reified T off to another generic function that isn't itself reified for that parameter.

Back to Generics & Variance