Delegation vs inheritance decision in Kotlin - Performance Comparison
When choosing between delegation and inheritance, it's important to understand how the program's work grows as it uses these techniques.
We want to see how the cost of running code changes with input size depending on delegation or inheritance.
Analyze the time complexity of the following Kotlin code using delegation.
interface Printer {
fun print()
}
class RealPrinter : Printer {
override fun print() {
println("Printing document")
}
}
class DelegatingPrinter(private val printer: Printer) : Printer by printer
fun main() {
val realPrinter = RealPrinter()
val printer = DelegatingPrinter(realPrinter)
printer.print()
}
This code uses delegation to forward the print call to another object.
Look for loops, recursion, or repeated calls that affect performance.
- Primary operation: The print() method call is forwarded once per call.
- How many times: Each print() call triggers exactly one delegated print() call.
Each print call results in one delegated print call, so the work grows directly with the number of calls.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 10 print calls, 10 delegated calls |
| 100 | 100 print calls, 100 delegated calls |
| 1000 | 1000 print calls, 1000 delegated calls |
Pattern observation: The number of operations grows linearly with the number of print calls.
Time Complexity: O(n)
This means the time to complete grows directly in proportion to how many times the method is called.
[X] Wrong: "Delegation adds extra hidden loops or slows down the program exponentially compared to inheritance."
[OK] Correct: Delegation simply forwards calls one-to-one without extra loops, so it does not increase the number of operations beyond the calls made.
Understanding how delegation and inheritance affect the number of operations helps you explain design choices clearly and confidently in interviews.
What if the delegated method itself contained a loop over input data? How would that change the time complexity?
Practice
Solution
Step 1: Understand inheritance purpose
Inheritance models an "is-a" relationship where a subclass extends a superclass.Step 2: Match the correct use case
Using inheritance is appropriate when a class is a specialized version of another class.Final Answer:
When a class is a specialized type of another class ("is-a" relationship). -> Option CQuick Check:
Inheritance = "is-a" relationship [OK]
- Confusing delegation with inheritance
- Using inheritance for code reuse only
- Thinking inheritance hides implementation
Printer by class Document?Solution
Step 1: Recall Kotlin delegation syntax
Kotlin uses: Interface by instanceto delegate interface implementation.Step 2: Identify correct syntax
class Document(val printer: Printer) : Printer by printer correctly shows delegation withclass Document(val printer: Printer) : Printer by printer.Final Answer:
class Document(val printer: Printer) : Printer by printer -> Option BQuick Check:
Delegation syntax =: Interface by instance[OK]
- Using extends keyword (not Kotlin)
- Writing implements instead of by
- Missing val/var for delegated instance
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) {
println("Console: $message")
}
}
class FileLogger : Logger {
override fun log(message: String) {
println("File: $message")
}
}
class Service(logger: Logger) : Logger by logger
fun main() {
val service = Service(ConsoleLogger())
service.log("Start")
}Solution
Step 1: Understand delegation in Service class
Service delegates Logger interface to the passed logger instance, here ConsoleLogger.Step 2: Trace the log call
Calling service.log("Start") calls ConsoleLogger.log, printing "Console: Start".Final Answer:
Console: Start -> Option AQuick Check:
Delegation calls ConsoleLogger.log = Console: Start [OK]
- Confusing which logger is used
- Expecting FileLogger output
- Thinking delegation copies methods
interface Worker {
fun work()
}
class Employee : Worker {
override fun work() {
println("Employee working")
}
}
class Manager(worker: Worker) : Worker by worker {
fun manage() {
println("Managing team")
}
}
fun main() {
val manager = Manager()
manager.work()
manager.manage()
}Solution
Step 1: Check Manager constructor usage
Manager requires a Worker parameter in constructor but none is provided in main().Step 2: Identify compilation error
Calling Manager() without argument causes a compilation error due to missing parameter.Final Answer:
Manager class missing argument for Worker in constructor call. -> Option AQuick Check:
Constructor argument missing = compilation error [OK]
- Ignoring constructor parameters
- Confusing delegation syntax with inheritance
- Assuming manage() is inaccessible
SmartDevice that can either behave like a Phone or a Camera depending on user choice at runtime. Which approach is best and why?Solution
Step 1: Analyze design needs
SmartDevice needs to switch behavior dynamically between Phone and Camera at runtime.Step 2: Choose delegation for flexibility
Delegation allows SmartDevice to hold and switch between Phone or Camera instances easily without inheritance constraints.Step 3: Exclude other options
Inheritance is static and cannot switch base class at runtime; Kotlin does not support multiple inheritance; static methods do not provide instance behavior.Final Answer:
Use delegation to delegate Phone or Camera behavior to SmartDevice dynamically. -> Option DQuick Check:
Dynamic behavior switching = delegation [OK]
- Trying to use inheritance for runtime behavior switch
- Assuming Kotlin supports multiple inheritance
- Using static methods instead of instance delegation
Ready to prove it?
Pass the test and this pattern moves unknown
