home writings uses think in code

🧵 Merging Two Sorted Linked Lists in Kotlin: From Trial-and-Error to Clean Elegance

Apr 17, 2025

tl;dr: Code that works is good. Code that reads like a story is better.

💡 Why I Picked This Problem

I’ve been solving problems on LeetCode lately to sharpen my Kotlin and data structure skills. When I came across “Merge Two Sorted Lists”, I thought: Easy. Just compare and connect, right?

Well… not quite.

What looked simple on the surface turned into a mini journey of learning how to:

  • Use dummy nodes
  • Handle linked list edge cases
  • Write idiomatic Kotlin
  • And most importantly, go from working code to elegant code

This blog captures that journey.

🛠️ The Problem

You’re given two sorted singly linked lists. Merge them into one sorted list and return it. The result should reuse the nodes from the original lists — no creating new nodes.

❌ My First Attempt (Functional but Flawed)

class Solution { fun mergeTwoLists(list1: ListNode?, list2: ListNode?): ListNode? { val result = ListNode() var l1 = list1 var l2 = list2 while(l1?.val != null && l2?.val != null){ if(l1.val <= l2.val){ result.next = l1 l1 = l1.next } else { result.next = l2 l2 = l2.next } } result.next = l1?.next ?: l2?.next return result.next } } // ListNode class definition for context class ListNode(var val: Int = 0) { var next: ListNode? = null } fun main() { // This code won't run correctly due to the flaws discussed below println("This is my first flawed attempt - see explanation below") }

At first glance, this feels like it should work… until you test it.

❗ What Went Wrong:

  1. I was overwriting result.next on every iteration — never building a full list.

  2. I forgot to track the tail of the result list.

  3. I tried to compare val properties safely, but added unnecessary checks.

  4. My final result.next = l1?.next ?: l2?.next skipped nodes!

This wasn’t just a logic fail — it was a reminder that linked lists don’t forgive sloppy thinking.

🔁 Refactoring and Debugging

I went back to the drawing board and broke it into smaller pieces:

✅ Use a dummy node to simplify list-building
✅ Use a tail pointer to track where we’re adding next
✅ Use safe Kotlin null-checking with ?:
✅ Replace manual pointer updates with Kotlin’s also

That led to something much more elegant.

✅ The Final Clean Kotlin Version

class Solution { fun mergeTwoLists(list1: ListNode?, list2: ListNode?): ListNode? { val dummy = ListNode(-1) var tail = dummy var l1 = list1 var l2 = list2 while (l1 != null && l2 != null) { if (l1.`val` <= l2.`val`) { tail.next = l1.also { l1 = it.next } } else { tail.next = l2.also { l2 = it.next } } tail = tail.next!! } tail.next = l1 ?: l2 return dummy.next } } // ListNode class definition class ListNode(var `val`: Int = 0) { var next: ListNode? = null } fun main() { // Helper function to create a linked list from an array fun createLinkedList(arr: IntArray): ListNode? { if (arr.isEmpty()) return null val dummy = ListNode(0) var current = dummy for (value in arr) { current.next = ListNode(value) current = current.next!! } return dummy.next } // Helper function to print a linked list fun printLinkedList(head: ListNode?) { var current = head val result = mutableListOf<Int>() while (current != null) { result.add(current.`val`) current = current.next } println(result.joinToString(" -> ")) } // Test case 1 val list1 = createLinkedList(intArrayOf(1, 2, 4)) val list2 = createLinkedList(intArrayOf(1, 3, 4)) println("List 1:") printLinkedList(list1) println("List 2:") printLinkedList(list2) val solution = Solution() val merged = solution.mergeTwoLists(list1, list2) println("Merged list:") printLinkedList(merged) }

🧠 What I Love About This Version

✅ Uses Kotlin’s also to update references cleanly

✅ Safe handling of null pointers

✅ Dummy node pattern makes it easier to return the result

✅ tail clearly communicates intent — appending to the end of the list

✅ The loop reads naturally — like you’re telling Kotlin what to do in plain English

This version passes all test cases and feels solid, simple, and elegant — a big leap from where I started.

💬 Key Takeaways

  • Don’t rush linked list problems — edge cases matter more than you think.

  • Kotlin gives you powerful tools to write safe and readable code — use them.

  • Start with “does it work” — but always push toward “does it feel clean”

  • The also block trick is totally underrated.

🔭 What’s Next?

I’m considering a follow-up where I:

  • Write a recursive version of this function in Kotlin
  • Generalize this logic to merge k sorted lists
  • Build a Kotlin-style unit test suite to validate edge cases

📢 Final Thoughts

There’s something deeply satisfying about taking messy, trial-and-error code and shaping it into something that’s both correct and beautiful. This little problem — just merging two lists — reminded me that code can be art when you care enough to polish it.

Thanks for reading!

Want to learn more about Kotlin and data structures? Let's connect and solve problems together!