Kotlin for Coding Interviews: The Cheat Sheet You'll Actually Use

May 26, 202611 min read
interview-prepdsaalgorithmskotlin
Kotlin for Coding Interviews: The Cheat Sheet You'll Actually Use
TL;DR
  • Fluency beats features: use Kotlin if you write it daily, especially for Android roles; only switch to Python if you're more comfortable there
  • ArrayDeque replaces both java.util.Stack and LinkedList: O(1) amortized, no nulls, no synchronization overhead
  • PriorityQueue is a min-heap by default: pass reverseOrder() for max-heap; compareBy chains cleanly for multi-criteria ordering
  • Int overflows silently at 2,147,483,647: default to Long for products, path sums, or any constraint near 10^9
  • IntArray is unboxed (int[]), Array<Int> is boxed (Integer[]): use IntArray for DP arrays, graphs, and anything performance-sensitive
  • tailrec gives free tail-call optimization: the compiler rewrites eligible recursion into a loop, eliminating stack overflow risk
  • TreeMap beats HashMap + manual sorting: floorKey/ceilingKey/firstKey/lastKey handle nearest-value and range queries in O(log n)

Your interview is in 48 hours. You've written Kotlin for two years. Some well-meaning recruiter told you Python is the language for LeetCode, and now you're staring at a heapq tutorial at midnight, learning a new standard library in the language you half-know.

Don't do that. Use the language you already know cold.

Fluency beats features. You know the footguns. You'll type faster. You'll spend zero mental overhead on syntax and all of it on the algorithm, which is the part that gets you hired.

This Kotlin for coding interviews guide covers the data structures that matter, the gotchas that trip people up, and a cheat sheet you can internalize in one session.

Should You Use Kotlin for Coding Interviews?

Use Kotlin if you're interviewing for Android roles or already write it daily. In a 45-minute interview, a language you know cold is worth more than a "better" language you half-know.

If you're targeting general SWE roles at FAANG, Python wins on raw conciseness. What takes three lines in Python often takes seven in Kotlin. That matters when you're down to eight minutes and the interviewer is watching your cursor blink. But if Python is foreign to you and Kotlin isn't, run with Kotlin. Most interviewers don't care which language you use as long as you can explain what you're doing. (There will be one who cares deeply. Most won't.)

Kotlin runs on the JVM, so the standard library is a mix of Kotlin-native types (ArrayDeque, MutableList) and Java interop types (PriorityQueue, TreeMap). The mixing is mostly invisible, but it creates a few traps worth knowing.

The Collection Decision Table

NeedUseAvoid
Stack or queueArrayDequejava.util.Stack, LinkedList
Min-heap / max-heapPriorityQueueManual sorting
Sorted map with navigationTreeMapHashMap + sorting later
Sorted setTreeSetHashSet + sorting later
Frequency mapmutableMapOf() + getOrDefaultManual if-else every time
Resizable arraymutableListOf() or ArrayList()Array<Int> for growing lists
Fixed-size primitive arrayIntArray(n)Array<Int> for primitives

Stack and Queue: Never Use java.util.Stack Again

The old Java Stack class is synchronized. It acquires a mutex lock on every push and pop, in case your binary tree traversal needed to be thread-safe for some reason. You don't need that, and you don't need its ancient Vector-based API. Kotlin's ArrayDeque is the right answer for both stacks and queues.

val stack = ArrayDeque<Int>() stack.addLast(1) // push stack.removeLast() // pop stack.last() // peek val queue = ArrayDeque<Int>() queue.addLast(1) // enqueue queue.removeFirst() // dequeue queue.first() // peek front

ArrayDeque is O(1) amortized for all four operations and doesn't allow nulls, which eliminates a category of subtle bugs. The no-nulls rule matters: if your graph has nullable node values, store them in a wrapper, not directly in the deque.

For BFS you can also use java.util.LinkedList as a queue, but ArrayDeque is faster in practice because of cache locality. Sequential memory wins over pointer chasing, same as arrays beat linked lists in traversal benchmarks.

Priority Queues Done Right

Kotlin doesn't have its own priority queue, so you use Java's. It's a min-heap by default.

// Min-heap (default) val minHeap = PriorityQueue<Int>() // Max-heap val maxHeap = PriorityQueue<Int>(reverseOrder()) // Custom comparator: sort pairs by second element descending val pq = PriorityQueue<Pair<Int, Int>>(compareByDescending { it.second }) minHeap.offer(3) val smallest = minHeap.poll() // removes and returns min val peek = minHeap.peek() // looks without removing

The bug that has derailed more interview solutions than null pointers: PriorityQueue is a min-heap by default. You need a max-heap. You pass nothing. Your k-th largest answer comes out backward and correct-looking on small test cases. Pass reverseOrder() or compareByDescending { it }. Do not negate values as a workaround unless you can explain why the domain allows it.

peek() on an empty queue returns null rather than throwing. Guard with isNotEmpty() before you poll.

When you need to sort by multiple criteria, compareBy composes cleanly:

// Sort by frequency ascending, then by value descending on tie val pq = PriorityQueue<Int>(compareBy<Int> { freq[it] }.thenByDescending { it })

TreeMap for Ordered Operations

TreeMap is a red-black tree. It keeps keys sorted and gives you O(log n) navigation operations that HashMap simply can't do.

val map = TreeMap<Int, String>() map[5] = "five" map[3] = "three" map[8] = "eight" map.floorKey(6) // 5 (largest key <= 6) map.ceilingKey(6) // 8 (smallest key >= 6) map.firstKey() // 3 map.lastKey() // 8 map.headMap(6) // submap with keys < 6 map.tailMap(6) // submap with keys >= 6

Reach for TreeMap any time the problem involves "nearest value", "next greater element in a set", or "sliding window with range queries". The navigation methods replace binary search on a sorted array you'd otherwise have to maintain manually.

TreeMap uses about 3x more memory than HashMap for the same keys. Rarely a constraint in a 45-minute interview, but worth mentioning when you walk through trade-offs.

The Int Overflow Trap

Kotlin's Int is 32-bit. It overflows at 2,147,483,647. You hit this constantly: two-sum with large values, path sums in trees, product subarray.

Default to Long whenever the problem involves products, sums over large arrays, or constraints near 10^9.

// Wrong: overflows silently var product = 1 for (n in nums) product *= n // wraps to garbage if nums has large values // Right var product = 1L for (n in nums) product *= n.toLong()

The product of [100000, 100000, 100000] is 10^15. Kotlin's Int tops out at around 2.1 billion. The runtime won't warn you. It'll quietly wrap around to a negative number and look fine on small test cases. Kotlin doesn't silently promote Int to Long in arithmetic: if you multiply two Int values, the result is Int. Cast at least one operand first: n.toLong() * m.

IntArray vs Array<Int>: The Boxing Problem

val a = IntArray(5) // int[] on the JVM (no boxing, fast) val b = Array<Int>(5) { 0 } // Integer[] on the JVM (boxed, slower)

For performance-sensitive algorithms (sorting, DP arrays, graphs), use IntArray or LongArray. Array<Int> boxes each value into an Integer object, which costs allocation and pointer-chasing.

Two places it bites you:

  1. Arrays.sort(intArray) uses dual-pivot quicksort (fast, unstable). Array<Int>.sort() uses Timsort (stable). If you need stable sort, use sortedArray().
  2. Passing IntArray to a function expecting Array<Int> doesn't compile. Convert with .toTypedArray().

Sorting Without the Comparator Headache

Kotlin's sorting API is friendlier than Java's. You rarely need to write a Comparator explicitly.

list.sort() // in-place ascending list.sortDescending() // in-place descending val sorted = list.sortedBy { it } // returns new list data class Point(val x: Int, val y: Int) points.sortWith(compareBy({ it.x }, { it.y })) // x first, y breaks ties

sortWith(compareBy(...)) chains are stable and readable. One trap: sortedBy and sortedWith return a new list. For in-place, use sortBy and sortWith (no "ed").

Four Stdlib Functions That Actually Save Lines

getOrDefault is the clean frequency map pattern:

val freq = mutableMapOf<Char, Int>() for (c in s) freq[c] = freq.getOrDefault(c, 0) + 1

getOrPut handles nested structures without a null check:

// Group words by anagram key (no if-absent boilerplate) val grouped = mutableMapOf<String, MutableList<String>>() grouped.getOrPut(key) { mutableListOf() }.add(word)

zipWithNext compares adjacent pairs without manual indexing:

val isNonDecreasing = nums.zipWithNext().all { (a, b) -> a <= b }

groupBy collapses a list into a map by key in one line:

val anagramGroups = words.groupBy { it.toCharArray().sorted().joinToString("") }

These four cover a surprising amount of ground. Know them cold, because the Kotlin stdlib has 600+ extension functions and the interview room has no internet.

Tweet reading "Got a CV today and the guy literally listed one of his skills as 'googling'. We're interviewing him."

The only skill that gets banned from the interview room. Know your getOrDefault from memory.

tailrec: One Keyword, Free TCO

Kotlin supports tail call optimization via the tailrec modifier. The compiler rewrites eligible recursive functions into loops, eliminating stack overflow risk.

tailrec fun factorial(n: Int, acc: Long = 1L): Long = if (n <= 1) acc else factorial(n - 1, acc * n)

The function must call itself as the very last operation with no further computation on the result. return 1 + tailrecFn(n - 1) is not tail-recursive and tailrec won't help. The compiler warns you when the keyword is present but the structure isn't actually eligible. Pay attention to that warning.

This matters for deep tree traversals where recursion depth could hit the JVM's default stack limit.

When Kotlin Slows You Down

No language is free. Kotlin has a few interview-specific costs worth knowing before you walk in.

Null safety is a tax in interview code. The ?. and ?: operators are great in production, but in a 45-minute session you're writing throwaway code. Every ?.let { } and ?: return adds cognitive load when you're already tracking two pointers and an edge case. If you find yourself writing node?.left?.let { queue.add(it) }, just write explicit null checks.

The standard library is large and hard to recall under pressure. If you can't remember whether it's removeFirst() or pollFirst() or pop(), you'll waste time. Stick to a small, memorized subset. The cheat sheet below is exactly that.

Kotlin doesn't have a built-in heap as ergonomic as Python's heapq. You're using Java's PriorityQueue, which requires explicit comparators and has the min/max footgun. Not a dealbreaker, but more typing than heapq.heappush.

Interviewers who don't know Kotlin may misread your code. Rare at companies that use Kotlin internally, but worth asking your recruiter about upfront.

Bar chart titled "Skills needed to get a job" showing a small bar for "Skills to do the job" versus a very tall bar for "Interview skills"

The ?.let {} syntax that works great at your day job becomes a liability when the clock is running.

Quick-Reference Cheat Sheet

// ---- Stack / Queue ---- val stack = ArrayDeque<Int>() stack.addLast(x); stack.removeLast(); stack.last() val queue = ArrayDeque<Int>() queue.addLast(x); queue.removeFirst(); queue.first() // ---- Heap ---- val minH = PriorityQueue<Int>() val maxH = PriorityQueue<Int>(reverseOrder()) val custH = PriorityQueue<Pair<Int,Int>>(compareBy { it.first }) heap.offer(x); heap.poll(); heap.peek() // ---- Sorted Map ---- val tm = TreeMap<Int, String>() tm.floorKey(k); tm.ceilingKey(k); tm.firstKey(); tm.lastKey() // ---- Arrays ---- val arr = IntArray(n) { 0 } // primitive, fast val arr2 = Array(n) { mutableListOf<Int>() } // object array // ---- Frequency Map ---- val freq = mutableMapOf<Int, Int>() for (x in arr) freq[x] = freq.getOrDefault(x, 0) + 1 // ---- Sorting ---- arr.sort() // IntArray, in-place list.sortWith(compareBy({ it.x }, { it.y })) // ---- Overflow guard ---- val product = a.toLong() * b // always cast before multiply // ---- Ranges ---- if (i in 0 until n) { } // bounds check for (i in n - 1 downTo 0) { } // reverse loop // ---- tailrec ---- tailrec fun dfs(node: TreeNode?, acc: Int): Int = ...

Key Takeaways

  • Use Kotlin if you're already fluent, especially for Android roles. Switch to Python if you're less comfortable with it.
  • ArrayDeque replaces both java.util.Stack and LinkedList for stacks and queues.
  • PriorityQueue is a min-heap by default. Pass reverseOrder() for a max-heap.
  • Int overflows silently. Default to Long for products and large sums.
  • IntArray is unboxed and fast. Array<Int> is boxed. Know the difference.
  • tailrec gives you free TCO for eligible recursive functions.

If you want to pressure-test these patterns under actual interview conditions, SpaceComplexity runs voice-based DSA mock interviews with rubric feedback, including scoring on communication and edge case handling, the two dimensions LeetCode grinding doesn't train.

Further Reading

Also useful from this blog: