-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
142: Whiteboard Practice: Implementing a Linked List in Kotlin
A few years ago, I interviewed a candidate who was a powerhouse with the Android framework. They could build a complex UI in their sleep and understood Coroutines inside and out. But when I asked them to implement a simple singly linked list on the whiteboard, they froze. They tried to use an ArrayList to simulate the behavior, which completely defeated the purpose of the exercise. It was a wake-up call for them—and a reminder for me—that there is a massive difference between knowing how to use a library and understanding the memory structures that make those libraries work. You might never write a custom linked list in your day-to-day job, but thinking through the pointers is the best way to sharpen your mental model of how objects live in the heap.
Building the Atomic Node
At its core, a linked list isn't a single entity; it's a chain of small, independent objects. In Kotlin, we represent this with a Node class. Since we want our list to be flexible, we'll use a generic type T. The key here is the next property. It has to be nullable because the very last node in your chain points to nothing—the end of the line.
data class Node<T>(
var value: T,
var next: Node<T>? = null
)
I prefer using a data class here for simplicity, though a regular class works too. The most important thing to remember is that next is a reference. When you change next, you aren't moving the data; you're just changing where the "arrow" points.
Wiring the List Logic
Now we need a wrapper class to manage these nodes. You don't want the user of your list to have to manually track the head (the first node). The LinkedList class handles the entry point and the logic for traversing the chain. While you could just track the head, I always recommend keeping a tail reference as well. Without a tail, adding an element to the end of the list becomes an O(n) operation because you have to walk through every single node just to find the end. With a tail, it's O(1).
class MyLinkedList<T> {
private var head: Node<T>? = null
private var tail: Node<T>? = null
fun add(value: T) {
val newNode = Node(value)
if (head == null) {
head = newNode
tail = newNode
} else {
// Attach the new node to the current tail and move the tail forward
tail?.next = newNode
tail = newNode
}
}
fun printList() {
var current = head
while (current != null) {
print("${current.value} -> ")
current = current.next
}
println("null")
}
}
Navigating the Nullability Minefield
The "whiteboard" part of this exercise is where most developers stumble: handling the edges. What happens if you try to remove an item from an empty list? What if the list only has one item? In Kotlin, the type system helps us, but you still have to be explicit about your null checks. When removing a node, you essentially have to "leapfrog" it. You find the node before the one you want to delete and point its next reference to the node after the target.
This is where the logic gets fiddly. If you're removing the head, the head moves to the second node. If you're removing the tail, you have to traverse the whole list again to find the new second-to-last node to update the tail reference. It's tedious, but that's exactly why this is a classic interview question—it tests your ability to handle state transitions without crashing the program.
📋 Practical Task
Exercise: Implement a Middle-Element Finder for MyLinkedList
In a real interview, you're rarely asked to just build the list; you're asked to perform an operation on it. Your task is to add a function called findMiddle() to the MyLinkedList class provided in the lesson.
The Constraints:
- You cannot use a counter to track the size of the list (no
list.size / 2). - You must use the "Fast and Slow Pointer" technique: one pointer moves one step at a time, while another moves two steps at a time. When the fast pointer reaches the end, the slow pointer will be exactly in the middle.
Requirements:
- Modify the
MyLinkedListclass to include thefindMiddle()method. - The method should return the value of the middle node, or
nullif the list is empty. - Test your implementation with lists of both even and odd lengths to ensure the pointer logic holds up.
There are no comments for now.