0
0
Kotlinprogramming~15 mins

Generic function declaration in Kotlin - Deep Dive

Choose your learning style9 modes available
Overview - Generic function declaration
What is it?
A generic function in Kotlin is a function that can work with different types without specifying the exact type in advance. It uses a placeholder type, called a type parameter, which is replaced with a real type when the function is called. This lets you write flexible and reusable code that works with many types safely. Instead of writing many similar functions for different types, you write one generic function that adapts.
Why it matters
Without generic functions, programmers would have to write many versions of the same function for each type they want to support, leading to repetitive and error-prone code. Generic functions solve this by allowing one function to handle many types, making code easier to maintain and less buggy. This flexibility is essential in real-world apps where data types vary but operations remain similar.
Where it fits
Before learning generic functions, you should understand basic Kotlin functions and type system concepts like classes and variables. After mastering generic functions, you can explore advanced topics like generic classes, variance, and type constraints to write even more powerful and safe code.
Mental Model
Core Idea
A generic function is like a recipe that uses placeholders for ingredients, letting you cook many dishes by swapping those ingredients without changing the recipe.
Think of it like...
Imagine a cookie cutter that can shape dough into any cookie shape you want. The cutter itself doesn't care about the dough type; it just shapes it. Similarly, a generic function shapes how data is handled without caring about the exact data type.
Generic Function Structure:

  fun <T> functionName(param: T): T {
      // Use param as type T
      return param
  }

Where:
  <T> = type parameter placeholder
  param = input of type T
  return = output of type T
Build-Up - 7 Steps
1
FoundationBasic function declaration in Kotlin
🤔
Concept: How to declare and use a simple function with a fixed type.
fun greet(name: String): String { return "Hello, $name!" } val message = greet("Anna") println(message)
Result
Hello, Anna!
Understanding how functions work with fixed types is essential before generalizing them with generics.
2
FoundationWhat is a type parameter?
🤔
Concept: Introducing the idea of a placeholder type that can represent any type.
fun identity(value: Int): Int { return value } // Type parameter replaces Int with T fun identityGeneric(value: T): T { return value } println(identity(5)) println(identityGeneric("Hi"))
Result
5 Hi
Recognizing that type parameters let functions accept any type, not just one fixed type.
3
IntermediateDeclaring a generic function
🤔Before reading on: Do you think the syntax for generic functions is the same as regular functions or different? Commit to your answer.
Concept: How to write a function with a type parameter using angle brackets before the function name.
fun echo(item: T): T { return item } println(echo(123)) println(echo("abc"))
Result
123 abc
Knowing the syntax for generic functions unlocks writing flexible code that works with any type.
4
IntermediateUsing multiple type parameters
🤔Before reading on: Can a generic function have more than one type parameter? Yes or no? Commit to your answer.
Concept: Generic functions can have multiple type parameters to handle different types simultaneously.
fun pairToString(a: A, b: B): String { return "$a and $b" } println(pairToString(10, "apples")) println(pairToString("John", 3.14))
Result
10 and apples John and 3.14
Understanding multiple type parameters allows combining different types in one generic function.
5
IntermediateType inference in generic functions
🤔Before reading on: Do you think Kotlin requires you to always specify the type parameter explicitly when calling a generic function? Commit to your answer.
Concept: Kotlin can often guess the type parameter from the arguments you pass, so you don't have to write it out.
fun printItem(item: T) { println(item) } printItem(42) // Kotlin infers T as Int printItem("hello") // Kotlin infers T as String
Result
42 hello
Knowing Kotlin's type inference reduces code clutter and makes generic functions easier to use.
6
AdvancedAdding type constraints to generics
🤔Before reading on: Can you restrict a generic function to accept only certain types? Yes or no? Commit to your answer.
Concept: You can limit the types a generic function accepts by specifying constraints, like requiring a type to implement an interface.
fun > maxOf(a: T, b: T): T { return if (a > b) a else b } println(maxOf(3, 7)) println(maxOf("apple", "banana"))
Result
7 banana
Understanding constraints helps write safer generic functions that only work with compatible types.
7
ExpertReified type parameters in inline functions
🤔Before reading on: Do you think generic type information is always available at runtime in Kotlin? Commit to your answer.
Concept: Normally, generic types are erased at runtime, but with inline and reified keywords, you can access the actual type inside the function.
inline fun isType(value: Any): Boolean { return value is T } println(isType("test")) // true println(isType("test")) // false
Result
true false
Knowing about reified types lets you write generic functions that can check or use type info at runtime, overcoming type erasure.
Under the Hood
Kotlin compiles generic functions using a technique called type erasure, which means the actual type information is removed at runtime to keep the bytecode simple and compatible with Java. The compiler replaces type parameters with Object or the first constraint type and inserts casts where needed. However, inline functions with reified type parameters keep the type info by copying the function code at each call site, allowing runtime type checks.
Why designed this way?
Type erasure was chosen to maintain compatibility with the Java Virtual Machine, which does not support generics at runtime. This design balances flexibility and performance but limits runtime type information. The inline and reified feature was added later to provide a way to access type info when needed without breaking compatibility.
Generic Function Compilation Flow:

Source Code with <T>  
       │
       ▼
Compiler replaces T with Object or constraint
       │
       ▼
Bytecode with casts and erased types
       │
       ▼
Runtime executes without generic type info

For inline + reified:

Source Code with <reified T>
       │
       ▼
Compiler copies function code per call
       │
       ▼
Bytecode with real type info available
       │
       ▼
Runtime can check actual types
Myth Busters - 4 Common Misconceptions
Quick: Do you think generic functions keep type information at runtime by default? Commit to yes or no.
Common Belief:Generic functions always know the exact type they work with at runtime.
Tap to reveal reality
Reality:Due to type erasure, generic type information is removed at runtime unless the function is inline with reified type parameters.
Why it matters:Assuming runtime type info exists can lead to bugs when trying to check or cast types inside generic functions.
Quick: Can you use any type as a generic parameter without restrictions? Commit to yes or no.
Common Belief:Generic functions accept any type without limits, so you can call them with any object.
Tap to reveal reality
Reality:You can add constraints to generic parameters to restrict acceptable types, ensuring safer code.
Why it matters:Ignoring constraints can cause runtime errors or logic bugs if incompatible types are used.
Quick: Do you think specifying type parameters explicitly is always required when calling generic functions? Commit to yes or no.
Common Belief:You must always write the type parameter explicitly when calling a generic function.
Tap to reveal reality
Reality:Kotlin usually infers the type parameters from the arguments, so explicit specification is often unnecessary.
Why it matters:Not knowing about type inference can make code unnecessarily verbose and harder to read.
Quick: Do you think generic functions can only have one type parameter? Commit to yes or no.
Common Belief:Generic functions are limited to a single type parameter.
Tap to reveal reality
Reality:Generic functions can have multiple type parameters to handle multiple types at once.
Why it matters:Believing this limits the design of flexible functions that work with multiple types.
Expert Zone
1
Generic functions with reified type parameters require the function to be inline, which affects how the function is compiled and inlined at call sites.
2
Type constraints can be combined with multiple bounds, allowing a type parameter to require several interfaces or classes simultaneously.
3
Variance (in/out) annotations do not apply directly to generic functions but are important when using generic classes or interfaces as parameters.
When NOT to use
Avoid generic functions when the operation depends heavily on specific type behavior that cannot be generalized or when performance-critical code requires avoiding boxing or casting overhead. In such cases, specialized functions or inline classes might be better.
Production Patterns
In production, generic functions are widely used in collections, utility libraries, and APIs to provide type-safe operations without code duplication. Patterns include generic extension functions, factory methods, and builders that adapt to many types while preserving safety.
Connections
Polymorphism
Generic functions build on the idea of polymorphism by allowing one function to work with many types, similar to how polymorphism lets objects behave differently based on their class.
Understanding polymorphism helps grasp how generic functions enable flexible code that adapts to different types without rewriting logic.
Templates in C++
Generic functions in Kotlin are conceptually similar to C++ templates, both allowing code to be written once and used with many types.
Knowing C++ templates can deepen understanding of how generics enable code reuse and type safety across languages.
Mathematical Functions
Generic functions resemble mathematical functions that work on any number or object type, abstracting the operation from the specific input.
Seeing generic functions as abstract mathematical mappings clarifies their role in creating general, reusable operations.
Common Pitfalls
#1Trying to check the type of a generic parameter at runtime without reified keyword.
Wrong approach:fun isString(value: Any): Boolean { return value is T // Error: Cannot check for generic type }
Correct approach:inline fun isString(value: Any): Boolean { return value is T }
Root cause:Type erasure removes generic type info at runtime, so normal generic functions cannot check types without reified.
#2Forgetting to specify type constraints when needed, causing unsafe operations.
Wrong approach:fun max(a: T, b: T): T { return if (a > b) a else b // Error: '>' not defined for T }
Correct approach:fun > max(a: T, b: T): T { return if (a > b) a else b }
Root cause:Without constraints, the compiler cannot guarantee that operations like '>' are valid for type T.
#3Explicitly specifying type parameters unnecessarily, cluttering code.
Wrong approach:fun printItem(item: T) { println(item) } printItem("hello")
Correct approach:fun printItem(item: T) { println(item) } printItem("hello")
Root cause:Not realizing Kotlin can infer type parameters from arguments leads to verbose and less readable code.
Key Takeaways
Generic functions let you write one function that works with many types by using type parameters as placeholders.
Kotlin uses type erasure, so generic type information is not available at runtime unless you use inline functions with reified type parameters.
You can add constraints to generic functions to restrict the types they accept, making your code safer and more predictable.
Kotlin often infers generic types automatically, so you usually don't need to specify them explicitly when calling generic functions.
Understanding generic functions is a key step toward writing flexible, reusable, and type-safe Kotlin code.