Coding Interview Questions

Reverse a string without using reversed().

Example

Two-pointer swap on a CharArray: O(n) time, O(n) space for the array.

fun reverse(s: String): String {
    val chars = s.toCharArray()
    var i = 0
    var j = chars.lastIndex
    while (i < j) {
        val tmp = chars[i]; chars[i] = chars[j]; chars[j] = tmp
        i++; j--
    }
    return String(chars)
}

fun main() {
    println(reverse("Kotlin"))
    println(reverse("Careeroza"))
    println(reverse(""))
}

Output

niltoK
azoreeraC

Check whether a string is a palindrome (ignore case and non-letters).

Example

fun isPalindrome(s: String): Boolean {
    val clean = s.lowercase().filter { it.isLetterOrDigit() }
    return clean == clean.reversed()
}

fun main() {
    listOf("Madam", "A man, a plan, a canal: Panama", "Kotlin").forEach {
        println("\"$it\" -> ${isPalindrome(it)}")
    }
}

Output

"Madam" -> true
"A man, a plan, a canal: Panama" -> true
"Kotlin" -> false

Print the first N Fibonacci numbers.

Example

Iterative (O(n), no recursion overflow) and a lazy generateSequence version.

fun fib(n: Int): List<Long> {
    val result = mutableListOf<Long>()
    var a = 0L
    var b = 1L
    repeat(n) {
        result.add(a)
        val next = a + b
        a = b; b = next
    }
    return result
}

fun main() {
    println(fib(10))
    val seq = generateSequence(0L to 1L) { (a, b) -> b to a + b }.map { it.first }
    println(seq.take(10).toList())
}

Output

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Count the frequency of each character in a string.

Example

groupingBy { }.eachCount() returns a LinkedHashMap, so the order of first appearance is kept.

fun main() {
    val text = "programming"
    val freq = text.groupingBy { it }.eachCount()
    println(freq)
    val firstNonRepeating = text.firstOrNull { freq[it] == 1 }
    println("First non-repeating: $firstNonRepeating")
    val mostCommon = freq.maxByOrNull { it.value }
    println("Most common: ${mostCommon?.key} (${mostCommon?.value} times)")
}

Output

{p=1, r=2, o=1, g=2, a=1, m=2, i=1, n=1}
First non-repeating: p
Most common: r (2 times)

Check whether two strings are anagrams.

Example

Sort-based is O(n log n); counting with an IntArray(26) is O(n).

fun isAnagram(a: String, b: String): Boolean {
    if (a.length != b.length) return false
    val count = IntArray(26)
    for (c in a.lowercase()) count[c - 'a']++
    for (c in b.lowercase()) count[c - 'a']--
    return count.all { it == 0 }
}

fun main() {
    println(isAnagram("listen", "silent"))
    println(isAnagram("Triangle", "Integral"))
    println(isAnagram("apple", "paper"))
    println("listen".toList().sorted() == "silent".toList().sorted())
}

Output

true
true
false
true

Find duplicate elements in a list.

Example

fun main() {
    val nums = listOf(4, 2, 7, 2, 9, 4, 4, 1)

    val seen = mutableSetOf<Int>()
    val dups = linkedSetOf<Int>()
    for (n in nums) if (!seen.add(n)) dups.add(n)
    println("Duplicates: $dups")

    val counts = nums.groupingBy { it }.eachCount().filter { it.value > 1 }
    println("With counts: $counts")
    println("Without duplicates: ${nums.distinct()}")
}

Output

Duplicates: [2, 4]
With counts: {4=3, 2=2}
Without duplicates: [4, 2, 7, 9, 1]

Find the second largest number in a list in one pass.

Example

Track the largest and second largest; handle duplicates and lists with fewer than two distinct values.

fun secondLargest(nums: List<Int>): Int? {
    var first: Int? = null
    var second: Int? = null
    for (n in nums) {
        if (first == null || n > first) {
            second = first
            first = n
        } else if (n != first && (second == null || n > second)) {
            second = n
        }
    }
    return second
}

fun main() {
    println(secondLargest(listOf(12, 35, 1, 10, 34, 1)))
    println(secondLargest(listOf(5, 5, 5)))
    println(secondLargest(listOf(10, 10, 9)))
    println(listOf(12, 35, 1, 10, 34).distinct().sortedDescending().getOrNull(1))
}

Output

34
null
9
34

Two Sum: return the indices of two numbers that add up to a target.

Example

A HashMap from value to index gives O(n) time instead of the O(n²) nested loop.

fun twoSum(nums: IntArray, target: Int): Pair<Int, Int>? {
    val indexOf = HashMap<Int, Int>()
    for ((i, n) in nums.withIndex()) {
        indexOf[target - n]?.let { return it to i }
        indexOf[n] = i
    }
    return null
}

fun main() {
    println(twoSum(intArrayOf(2, 7, 11, 15), 9))
    println(twoSum(intArrayOf(3, 2, 4), 6))
    println(twoSum(intArrayOf(1, 2), 10))
}

Output

(0, 1)
(1, 2)
null

Check whether brackets in a string are balanced.

Example

Push opening brackets on a stack (ArrayDeque), pop and compare on closing ones.

fun isBalanced(s: String): Boolean {
    val pairs = mapOf(')' to '(', ']' to '[', '}' to '{')
    val stack = ArrayDeque<Char>()
    for (c in s) {
        when (c) {
            '(', '[', '{' -> stack.addLast(c)
            ')', ']', '}' -> if (stack.removeLastOrNull() != pairs[c]) return false
        }
    }
    return stack.isEmpty()
}

fun main() {
    listOf("{[()]}", "([)]", "((", "fun main() { println(arr[0]) }").forEach {
        println("$it -> ${isBalanced(it)}")
    }
}

Output

{[()]} -> true
([)] -> false
(( -> false
fun main() { println(arr[0]) } -> true

Implement an LRU cache.

Example

A LinkedHashMap with accessOrder = true moves an entry to the end on every access; overriding removeEldestEntry evicts the least recently used entry. Android has a ready-made android.util.LruCache used for image caches.

class LruCache<K, V>(private val capacity: Int) :
    LinkedHashMap<K, V>(capacity, 0.75f, true) {
    override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?): Boolean =
        size > capacity
}

fun main() {
    val cache = LruCache<String, String>(3)
    cache["home"] = "home.png"
    cache["cart"] = "cart.png"
    cache["profile"] = "profile.png"
    cache["home"]                      // access: home becomes most recent
    cache["orders"] = "orders.png"     // evicts cart (least recently used)
    println(cache.keys)
}

Output

[profile, home, orders]

Fetch data from several APIs in parallel and combine the results.

Example

A classic Android coding question. async per call plus awaitAll() inside coroutineScope: if one call fails, the others are cancelled.

import kotlinx.coroutines.*

data class Profile(val name: String)

suspend fun fetchProfile(): Profile { delay(200); return Profile("Asha") }
suspend fun fetchOrders(): List<String> { delay(300); return listOf("#101", "#102") }
suspend fun fetchPrice(sku: String): Int { delay(100); return sku.length * 100 }

suspend fun loadDashboard(): String = coroutineScope {
    val profile = async { fetchProfile() }
    val orders = async { fetchOrders() }
    val prices = listOf("pen", "laptop", "bag").map { sku -> async { fetchPrice(sku) } }.awaitAll()
    "${profile.await().name} has ${orders.await().size} orders; cart total = ${prices.sum()}"
}

fun main(): Unit = runBlocking {
    println(loadDashboard())
}

Output

Asha has 2 orders; cart total = 1200