{"id":265,"date":"2025-03-21T14:10:07","date_gmt":"2025-03-21T06:10:07","guid":{"rendered":"https:\/\/blog.newstrong.xyz\/?p=265"},"modified":"2025-03-21T14:10:07","modified_gmt":"2025-03-21T06:10:07","slug":"prefer-function-reference-over-lambda-in-kotlin-wrong","status":"publish","type":"post","link":"https:\/\/newstrong.top\/index.php\/2025\/03\/21\/prefer-function-reference-over-lambda-in-kotlin-wrong\/","title":{"rendered":"Prefer Function Reference over Lambda in Kotlin? Wrong!"},"content":{"rendered":"<p>Using\u00a0<a href=\"https:\/\/vtsen.hashnode.dev\/complete-c-to-kotlin-syntax-comparisons#heading-lambda-methods-functions\">lambda<\/a>\u00a0as callback is very common in Jetpack compose, but do you know you can also use\u00a0<a href=\"https:\/\/kotlinlang.org\/docs\/reflection.html#function-references\">function reference<\/a>\u00a0to replace the lambda?<\/p>\n<p>When I first heard about this function reference, I thought the function reference must be better. Unlike anonymous function \/ lambda, it just a function pointer or reference to a function. It should run faster and use less memory, as it doesn&#8217;t allocate extra memory. So I replaced lambda with function reference whenever is possible. Well, I was wrong!<\/p>\n<p>After doing some researches, it turns out that\u00a0function reference\u00a0still allocates extra memory just like what lambda does, and it also\u00a0runs 2x slower than lambda. Before we look into why, let&#8217;s understand their usages and differences first with examples below.<\/p>\n<h2><a href=\"https:\/\/vtsen.hashnode.dev\/prefer-function-reference-over-lambda-in-kotlin-wrong#heading-lambda-call-the-objects-function\" title=\"Permalink\"><\/a>Lambda (call the object&#8217;s function)<\/h2>\n<p>Let&#8217;s say you have\u00a0<code>Screen<\/code>\u00a0and\u00a0<code>ViewModel<\/code>\u00a0classes below. The\u00a0<code>id<\/code>\u00a0is used to identify a different instance of\u00a0<code>ViewModel<\/code>.<\/p>\n<pre><code class=\"language-kotlin\">class Screen(private val callback: () -&gt; Unit) {\n    fun onClick() = callback()\n}\n\nclass ViewModel(var id: String) {\n    fun screenOnClick() {\n        println(&quot;ViewModel$id onClick() is called!&quot;)\n    }\n\n    fun screenOnClickDoNothing() {}\n}\n<\/code><\/pre>\n<p>In\u00a0<code>main()<\/code>, it creates the\u00a0<code>ViewModel<\/code>\u00a0object. Then, you pass in the lambda callback parameter &#8211;\u00a0<code>{ viewModel.screenOnClick() }<\/code>\u00a0to\u00a0<code>Screen<\/code>&#8216;s constructor.<\/p>\n<pre><code>fun main() {\n    var viewModel = ViewModel(&quot;1&quot;)\n    val screen = Screen {\n        viewModel.screenOnClick()\n    }\n    viewModel = ViewModel(&quot;2&quot;)\n    screen.onClick()\n}\n<\/code><\/pre>\n<blockquote>\n<p><code>viewModel.screenOnClick()<\/code>\u00a0is the object&#8217;s function.<\/p>\n<\/blockquote>\n<p>To demonstrate the difference between lambda and function reference, you update the\u00a0<code>viewModel<\/code>\u00a0variable with another new instance of\u00a0<code>ViewModel<\/code>\u00a0&#8211;\u00a0<code>viewModel = ViewModel(&quot;2&quot;)<\/code>\u00a0before calling\u00a0<code>screen.onClick()<\/code>.<\/p>\n<blockquote>\n<p>Calling\u00a0<code>screen.onClick()<\/code>\u00a0is to simulate screen callback<\/p>\n<\/blockquote>\n<p>Here is the output:<\/p>\n<pre><code>ViewModel2 onClick() is called!\n<\/code><\/pre>\n<p>Please note that\u00a0ViewModel2 is called instead of ViewModel1. It looks like lambda holds the reference to the\u00a0<code>viewModel<\/code>\u00a0variable. When the variable is updated, it is automatically reflected.<\/p>\n<h2><a href=\"https:\/\/vtsen.hashnode.dev\/prefer-function-reference-over-lambda-in-kotlin-wrong#heading-objects-function-reference\" title=\"Permalink\"><\/a>Object&#8217;s Function Reference<\/h2>\n<p>Let&#8217;s replace the lambda with a function reference &#8211;\u00a0<code>viewModel::screenOnClick<\/code>. The code looks like this.<\/p>\n<pre><code>fun main() {\n    var viewModel = ViewModel(&quot;1&quot;)\n    val screen = Screen(callback = viewModel::screenOnClick)\n    viewModel = ViewModel(&quot;2&quot;)\n    screen.onClick()\n}\n<\/code><\/pre>\n<p>Here is the output<\/p>\n<p>Copy<\/p>\n<p>Copy<\/p>\n<pre><code>ViewModel1 onClick() is called!\n<\/code><\/pre>\n<p>Please note that\u00a0ViewModel1 is called instead of ViewModel2. It looks like the function reference is caching the\u00a0<code>ViewModel<\/code>\u00a0instance when it is called. Thus, it is the old\u00a0<code>ViewModel<\/code>\u00a0instance and not the updated one. Wrong!<\/p>\n<p>If you don&#8217;t replace the\u00a0<code>viewModel<\/code>\u00a0instance but modifying\u00a0<code>ViewModel.id<\/code>\u00a0directly instead,<\/p>\n<pre><code>fun main() {\n    var viewModel = ViewModel(&quot;1&quot;)\n    val screen = Screen(callback = viewModel::screenOnClick)\n    viewModel.id = &quot;2&quot;\n    screen.onClick()\n}\n<\/code><\/pre>\n<p>you get the updated value.<\/p>\n<pre><code>ViewModel2 onClick() is called!\n<\/code><\/pre>\n<p>This tells you that function reference doesn&#8217;t cache the\u00a0<code>ViewModel<\/code>\u00a0instance, but holding its reference. If the original\u00a0<code>viewModel<\/code>\u00a0is replaced, the\u00a0<code>Screen<\/code>\u00a0still holds the old reference of the\u00a0<code>ViewModel<\/code>. Thus, it prints out the value of that old reference.<\/p>\n<h2><a href=\"https:\/\/vtsen.hashnode.dev\/prefer-function-reference-over-lambda-in-kotlin-wrong#heading-performance-test-objects-function\" title=\"Permalink\"><\/a>Performance Test (Object&#8217;s Function)<\/h2>\n<p>Let&#8217;s run some performance tests.<\/p>\n<p>This is the lambda test that creating\u00a0<code>Screen<\/code>\u00a01 billions times.<\/p>\n<pre><code>fun lambdaTest() {\n    val viewModel = ViewModel(&quot;1&quot;)\n\n    val timeMillis = measureTimeMillis {\n        repeat(1_000_000_000) {\n            val screen = Screen {\n                viewModel.screenOnClickDoNothing()\n            }\n\n            screen.onClick()\n        }\n    }\n    println(&quot;lambdaTest: ${timeMillis\/1000f} seconds&quot;)\n}\n<\/code><\/pre>\n<p>The output show it runs ~2.4 seconds.<\/p>\n<pre><code>lambdaTest: 2.464 seconds\n<\/code><\/pre>\n<p>Now, it is function reference&#8217;s turn.<\/p>\n<pre><code>fun funRefTest() {\n\n    val viewModel = ViewModel(&quot;1&quot;)\n\n    val timeMillis = measureTimeMillis {\n        repeat(1_000_000_000) {\n            val screen = Screen(\n                callback = viewModel::screenOnClickDoNothing\n            )\n\n            screen.onClick()\n        }\n    }\n    println(&quot;funRefTest: ${timeMillis\/1000f} seconds&quot;)\n}\n<\/code><\/pre>\n<p>The output shows it runs 5.2 seconds.<\/p>\n<pre><code>funRefTest: 5.225 seconds\n<\/code><\/pre>\n<p>This concludes that\u00a0lambda runs ~2x faster than function reference. Why? Let&#8217;s look at the decompiled code.<\/p>\n<h2><a href=\"https:\/\/vtsen.hashnode.dev\/prefer-function-reference-over-lambda-in-kotlin-wrong#heading-decompiled-code-in-java\" title=\"Permalink\"><\/a>Decompiled Code In Java<\/h2>\n<p>Let&#8217;s look at the decompiled code in Java. This is the simplified version without the\u00a0<code>measureTimeMillis<\/code>\u00a0and\u00a0<code>repeat<\/code>\u00a0code.<\/p>\n<p>lambdaTest()<\/p>\n<pre><code>public static final void lambdaTest() {\n  final ViewModel viewModel = new ViewModel();\n  Screen screen = new Screen((Function0)(new Function0() {\n\n     public Object invoke() {\n        this.invoke();\n        return Unit.INSTANCE;\n     }\n\n     public final void invoke() {\n        viewModel.screenOnClickDoNothing();\n     }\n  }));\n  screen.onClick();\n}\n<\/code><\/pre>\n<p>funRefTest()<\/p>\n<pre><code>public static final void funRefTest() {\n  ViewModel viewModel = new ViewModel();\n  Screen screen = new Screen((Function0)(new Function0(viewModel) {\n\n     public Object invoke() {\n        this.invoke();\n        return Unit.INSTANCE;\n     }\n\n     public final void invoke() {\n        ((ViewModel)this.receiver).screenOnClickDoNothing();\n     }\n  }));\n  screen.onClick();\n}\n<\/code><\/pre>\n<p>Few important things here:<\/p>\n<ul>\n<li>Function reference allocates new function memory just like the lambda &#8211;\u00a0<code>new Function0()<\/code>\u00a0and\u00a0<code>new Function0(viewModel)<\/code>. So it is not a function pointer.<\/li>\n<li>Function reference passes in the\u00a0<code>viewModel<\/code>\u00a0into\u00a0<code>Function0<\/code>\u00a0and lambda doesn&#8217;t<\/li>\n<\/ul>\n<p>Based on these findings, I suspect the passed-in\u00a0<code>ViewModel<\/code>\u00a0reference could be the overhead (requires extra copy to hold the reference?) that contributes to slowness in function reference.<\/p>\n<h2><a href=\"https:\/\/vtsen.hashnode.dev\/prefer-function-reference-over-lambda-in-kotlin-wrong#heading-what-about-non-objects-function\" title=\"Permalink\"><\/a>What about non-object&#8217;s function?<\/h2>\n<p>Let&#8217;s say you move\u00a0<code>screenOnClickDoNothing()<\/code>\u00a0out of\u00a0<code>ViewModel<\/code><\/p>\n<pre><code>fun screenOnClickDoNothing() {}\n<\/code><\/pre>\n<p>and call it directly &#8211; replace\u00a0<code>viewModel.screenOnClickDoNothing()<\/code>\u00a0with\u00a0<code>screenOnClickDoNothing()<\/code>\u00a0in lambda.<\/p>\n<pre><code>fun lambdaTest() {\n    val timeMillis = measureTimeMillis {\n        repeat(1_000_000_000) {\n            val screen = Screen {\n                screenOnClickDoNothing()\n            }\n\n            screen.onClick()\n        }\n    }\n    println(&quot;lambda1Test: ${timeMillis\/1000f} seconds&quot;)\n}\n<\/code><\/pre>\n<p>It takes 0.016 seconds to run.<\/p>\n<pre><code>lambdaTest: 0.016 seconds\n<\/code><\/pre>\n<p>What about function reference? Replace\u00a0<code>viewModel::screenOnClickDoNothing<\/code>\u00a0with\u00a0<code>::screenOnClickDoNothing<\/code><\/p>\n<pre><code>fun globalFunRefTest(){\n    val timeMillis = measureTimeMillis {\n        repeat(1_000_000_000) {\n            val screen = Screen(callback = ::screenOnClickDoNothing)\n            screen.onClick()\n        }\n    }\n    println(&quot;funRefTest: ${timeMillis\/1000f} seconds&quot;)\n}\n<\/code><\/pre>\n<p>and it takes the same amount of time as lambda.<\/p>\n<pre><code>funRefTest: 0.016 seconds\n<\/code><\/pre>\n<p>It makes senses because both have the same exact decompiled Java code.<\/p>\n<pre><code>public static final void lambdaTest() {\n    Screen screen = new Screen((Function0)null.INSTANCE);\n    screen.onClick();\n}\n\npublic static final void funRestTest() {\n    Screen screen = new Screen((Function0)null.INSTANCE);\n    screen.onClick();\n}\n<\/code><\/pre>\n<p>One important thing to note is, although lambda is used, there is no new function memory allocated. Maybe the compiler is smart enough to optimize it. Thus, it runs faster than lambda with object&#8217;s function in the previous examples.<\/p>\n<blockquote>\n<p>By the way, all the tests I ran so far is based on Kotlin compiler version 1.7.10.<\/p>\n<\/blockquote>\n<h2><a href=\"https:\/\/vtsen.hashnode.dev\/prefer-function-reference-over-lambda-in-kotlin-wrong#heading-conclusion\" title=\"Permalink\"><\/a>Conclusion<\/h2>\n<p>There is a slight different behavior between using lambda vs function reference, but I don&#8217;t see any practical use case to use either one of them. By guessing, I think lambda should cover 99% of use cases because it doesn&#8217;t require the object to be initialized first while registering the callback.<\/p>\n<p>Here is the summary of Lambda vs Function Reference behavior:<\/p>\n<table>\n<thead>\n<tr>\n<th>Lambda<\/th>\n<th>Function Reference<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Keeps the up-to-date object reference<\/td>\n<td>Keeps the copy of the object reference<\/td>\n<\/tr>\n<tr>\n<td>Does NOT require the object to be initialized<\/td>\n<td>Requires the object to be initialized<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Calling the function 1 billions times may not be practical, but it does prove that\u00a0function reference is NOT better than lambda\u00a0in Kotlin. Practically, it probably doesn&#8217;t matter which one you use. The difference is likely negligible.<\/p>\n<p>Maybe because I&#8217;m coming for C++\/C# background, I had an impression that function reference in Kotlin is like a function pointer. You hold the reference to the function, and you do not allocate new memory. No, it is not the case in Kotlin.<\/p>\n<p>Given this very little research I did,\u00a0I&#8217;ve decided to use lambda over function reference to implement callbacks\u00a0because it has less overhead. It also doesn&#8217;t reduce readability too much, in my opinion. Just the extra\u00a0<code>{ }<\/code>?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Using\u00a0lambda\u00a0as callback is very common in Jetpack comp&#46;&#46;&#46;<\/p>\n","protected":false},"author":1,"featured_media":71,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-265","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/newstrong.top\/index.php\/wp-json\/wp\/v2\/posts\/265","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/newstrong.top\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/newstrong.top\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/newstrong.top\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/newstrong.top\/index.php\/wp-json\/wp\/v2\/comments?post=265"}],"version-history":[{"count":0,"href":"https:\/\/newstrong.top\/index.php\/wp-json\/wp\/v2\/posts\/265\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/newstrong.top\/index.php\/wp-json\/wp\/v2\/media\/71"}],"wp:attachment":[{"href":"https:\/\/newstrong.top\/index.php\/wp-json\/wp\/v2\/media?parent=265"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/newstrong.top\/index.php\/wp-json\/wp\/v2\/categories?post=265"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/newstrong.top\/index.php\/wp-json\/wp\/v2\/tags?post=265"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}