Skip to content
Skilled.dev

Language preparation

Kotlin coding interview preparation: arrays, nullability, and JVM collections

Use primitive arrays, nullable results, map presence, JVM queues, safe comparisons, and explicit collection-sharing assumptions in Kotlin.

Published · 5 min read

Kotlin/JVM interview preparation should connect concise syntax to the collections and numeric operations it uses. A read-only List is a restricted view, a primitive IntArray has different storage from a boxed list, and a nullable map lookup can preserve a valid index of zero. Those details often decide whether a short solution matches its intended contract.

The examples target Kotlin on the JVM and use ordinary functions plus Java's ArrayDeque. They do not require Kotlin/Native, coroutines, or a third-party collection library. Match the exercise's starter types and configured compiler before introducing APIs that its environment may not provide.

Choose primitive arrays or collection views deliberately

IntArray stores primitive integer values. Array<Int> and List<Int> involve boxed integer elements on the JVM. Use the representation supplied by the exercise unless a conversion has a purpose. Converting an array into a list or repeatedly materializing a sequence adds work and storage that belong in the complexity analysis.

Kotlin separates read-only collection interfaces from mutable interfaces. A List parameter prevents writes through that view, but another reference may still mutate the underlying collection. Declaring a mutable collection with val prevents rebinding the variable; it does not make the collection's contents immutable.

The collections overview describes those interface distinctions. When an algorithm needs an independent result snapshot, copy the required storage rather than assuming a read-only type performed a copy.

Keep missing results and index zero separate

A HashMap from Long to Int can store earlier indexes for Two Sum. Because this map never stores null values, a null lookup means absent. An earlier index of zero passes the explicit null check.

import java.util.ArrayDeque

fun twoSum(values: IntArray, target: Int): IntArray? {
    val previous = HashMap<Long, Int>()
    for (index in values.indices) {
        val complement = target.toLong() - values[index].toLong()
        val earlier = previous[complement]
        if (earlier != null) return intArrayOf(earlier, index)
        previous[values[index].toLong()] = index
    }
    return null
}

fun bfsOrder(graph: Map<Int, List<Int>>, start: Int): List<Int> {
    val queue = ArrayDeque<Int>()
    val seen = HashSet<Int>()
    val order = ArrayList<Int>()
    seen.add(start)
    queue.addLast(start)
    while (queue.isNotEmpty()) {
        val node = queue.removeFirst()
        order.add(node)
        for (neighbor in graph[node].orEmpty()) {
            if (seen.add(neighbor)) queue.addLast(neighbor)
        }
    }
    return order
}

The pair search returns null for no answer and an independent two-element array for success. It checks before insertion, preserving distinct positions. Widening both operands before subtraction makes differences of Int inputs fit Long. It does not establish a safe policy for a future function accepting arbitrary Long values.

The graph is stable during traversal. A missing key has no outgoing neighbors, and the supplied list order controls discovery order. Marking when adding to the queue prevents cycles or converging paths from producing repeated entries. Expected traversal work is O(V + E) under the usual bounded-key hash model, with O(V) retained visited, queue, and output state.

Use nullability to describe the domain

The Elvis operator supplies a fallback when an expression is null. It is appropriate when that fallback matches the domain, such as an empty neighbor list for a missing adjacency key. A missing cached answer may need a different treatment if null is itself a valid computed result.

For a map allowing nullable payloads, map[key] == null cannot always distinguish a missing entry from a present null. Use containsKey when presence matters. The example avoids that ambiguity by storing non-null indexes.

Prefer a successful null check to an unexplained !!. A not-null assertion changes the failure behavior to an exception if the assumption is wrong; it does not validate a graph or prove a recursive child exists. Write the base case or presence branch where the algorithm needs it.

Compare values with the right operation

Kotlin's == delegates to equality, while === asks about reference identity. Arrays need content comparison such as contentEquals for their element values; writing a == b for two IntArray results does not establish equal contents. This matters in tests that compare a returned pair with an expected array.

If multiple pairs are valid, even contentEquals with one reference pair is too restrictive. Check bounds, distinct indexes, and the target sum. For graph labels represented by objects, define equality and hashing consistently with the vertex identity the problem uses.

A data class can be a useful immutable coordinate key when its fields have the intended equality. Mutable key fields can invalidate a hash lookup after insertion. Saving a mutable object in a copied list still shares that object with other references.

Check numeric and text boundaries

Int and Long arithmetic need a range argument. A comparator based on a - b can overflow; compareValues or an appropriate comparator expresses order without that subtraction. Integer division truncation also needs attention in midpoint and partition calculations, especially when negative values are involved.

Kotlin/JVM strings and Char values use UTF-16 code units. Iterating ordinary characters is sufficient for an ASCII-constrained exercise, but supplementary code points can span two Char values. Java's code-point APIs can support a code-point model when that is what the problem requests. Grapheme clusters require another explicit segmentation choice.

Repeated substring calls and collection transformations can hide copying. A concise chain of map, filter, and sorted may allocate intermediate collections. Use it when the resulting work is acceptable, and count the actual passes rather than equating fewer source lines with a lower complexity bound.

Plan stack depth and state restoration

A recursive traversal consumes JVM call-stack space. A chain-shaped tree or graph can exceed the available stack even if its asymptotic algorithm is linear. The explicit ArrayDeque in the example gives a queue-based alternative; using removeLast instead would implement a different stack schedule.

For backtracking, restore a mutable path after each recursive call and save an independent path copy when producing a result. Initialize grid rows separately, for example with a row-creation lambda, rather than returning the same mutable row object for every position. Nested reference sharing can make several apparent cells change at once.

Build a small boundary test set

Compare Two Sum with exhaustive pairs using Long addition. Include empty input, a duplicate pair, a valid zero index, no answer, and Int extremes. Compare returned array contents only when the task specifies one answer; otherwise validate the pair contract.

For BFS, check cycles, shared neighbors, missing keys, and a one-vertex graph. Verify input neighbor lists are unchanged. Then solve Two Sum, BFS, and permutations in Kotlin when the editor offers it. Explain where null can occur, which collections can mutate, and which values remain shared between branches.

Worked lesson walkthroughs

58 reading lessons with code examples in this language.

Concepts

Concept lesson

Arrays in Kotlin

Learn array insertion in Kotlin. Trace a copied result, explain shifting costs, and check empty inputs and endpoint positions without changing the original.

Concept lesson

Big O time and space complexity in Kotlin

Analyze time and space complexity in Kotlin with a suffix-copy example. Count nested work, returned storage, and allocation instead of counting loops alone.

Concept lesson

Hash Tables in Kotlin

Count frequencies with a hash map in Kotlin. Preserve input order, distinguish missing keys from zero, and explain expected lookup time and retained storage.

Concept lesson

Strings in Kotlin

Practice string construction in Kotlin with a repeat-text example. Explain builders, copies, Unicode representation, output size, and boundary cases.

Concept lesson

Palindromes, permutations, and anagrams in Kotlin

Check an ASCII-normalized palindrome in Kotlin. Trace two-pointer comparisons, case handling, ignored characters, and the limits of the text contract.

Concept lesson

Recursion in Kotlin

Trace recursive factorial in Kotlin from the base case through returning calls. Explain termination, stack space, integer limits, and boundary tests.

Concept lesson

Logarithmic time in Kotlin: repeated halving and search

Understand logarithmic loops in Kotlin by counting integer halvings. Test powers of two, explain rounding, and derive the exact step count.

Concept lesson

Binary search in Kotlin: invariants and boundary tests

Write first-occurrence binary search in Kotlin. Trace half-open bounds, duplicate values, missing targets, and the random-access assumption behind O(log n).

Concept lesson

Sorting in Kotlin

Return a numerically sorted copy in Kotlin. Preserve duplicates and the original input, choose the right library operation, and account for copied storage.

Concept lesson

Linked Lists in Kotlin

Find a linked list's middle in Kotlin with slow and fast runners. Define the even-length result, handle an empty list, and separate construction from traversal.

Concept lesson

Pointers in Kotlin

Trace shared mutation and reference rebinding in Kotlin. Follow two aliases through one object update and a new allocation, with explicit ownership notes.

Concept lesson

Queues in Kotlin

Practice FIFO queue behavior in Kotlin. Compare efficient front removal with a head cursor, preserve repeated values, and count fill-and-drain costs.

Concept lesson

Stacks in Kotlin

Practice LIFO stack behavior in Kotlin. Trace push and pop, separate empty state from zero payloads, and explain node ownership and end-operation costs.

Concept lesson

Trees in Kotlin

Compute binary-tree edge height in Kotlin. Read heap-indexed nullable slots, handle an empty root, and account for reachable nodes and recursion depth.

Concept lesson

Tree traversal in Kotlin: in-order, pre-order, and post-order

Trace in-order binary-tree traversal in Kotlin. Explain nullable child positions, visit order, recursion space, and when the output is actually sorted.

Concept lesson

Binary Search Trees in Kotlin

Search a binary search tree in Kotlin. Follow one ordered path, handle absent children, and relate search cost to height without assuming balance.

Concept lesson

Graphs in Kotlin

Read graph adjacency lists in Kotlin and compute out-degrees. Define vertex IDs, parallel edges, self-loops, representation costs, and input assumptions.

Concept lesson

Depth-First Search in Kotlin

Implement iterative DFS in Kotlin. Preserve listed neighbor order, skip repeated visits, trace a shared descendant, and explain pending-stack costs.

Concept lesson

Breadth-First Search in Kotlin

Implement BFS in Kotlin with FIFO order and enqueue-time discovery. Trace graph layers, cycles, shared descendants, and visited-storage costs.

Concept lesson

Subproblems and Overlapping Subproblems in Kotlin

Count naive Fibonacci calls in Kotlin. Trace overlapping states, include base-case work, and identify the cache key before applying memoization.

Concept lesson

Memoization in Kotlin

Memoize Fibonacci in Kotlin and count distinct computed states. Preserve cached zero, define cache lifetime, and explain remaining recursion depth.

Concept lesson

Bottom-Up vs. Top-Down in Kotlin

Build a bottom-up Fibonacci table in Kotlin. Follow dependency order, test zero input, and compare full-table storage with rolling-state alternatives.

Concept lesson

Dynamic Programming in Kotlin

Solve minimum coin count in Kotlin. Trace the recurrence, represent unreachable states, test a greedy counterexample, and explain amount-based table costs.

Data structure implementations

Implementation tutorial

Implement a dynamic array in Kotlin

Implement a map-backed teaching array in Kotlin. Trace push, pop, insertion, and removal, including logical length, missing indices, and obsolete entries.

Implementation tutorial

Implement a hash table in Kotlin: collisions, updates, and removal

Build a hash table in Kotlin with separate chaining. Follow colliding keys through lookup, update, and deletion, and explain the limits of a fixed teaching hash.

Implementation tutorial

Implement a linked list in Kotlin: head, tail, and node identity

Build a doubly linked list in Kotlin. Trace prepend and append, verify reciprocal links and both endpoints, and explain ownership and retention.

Implementation tutorial

Implement a queue in Kotlin: FIFO links and empty transitions

Implement a linked queue in Kotlin. Trace FIFO order, the last removal, and refilling an empty queue while preserving first and last endpoint invariants.

Implementation tutorial

Implement a stack in Kotlin: push, pop, and ownership

Implement a linked stack in Kotlin. Follow the top link through push, peek, and pop, including empty state, returned nodes, and retained chains.

Implementation tutorial

Implement a binary search tree in Kotlin: insert and search

Build a binary search tree in Kotlin. Trace insertion and search, preserve duplicate keys on the right, and test skewed shapes without assuming balancing.

Implementation tutorial

Implement a graph in Kotlin: adjacency lists and edge removal

Build an adjacency-list graph in Kotlin. Trace directed and undirected edges, self-loops, parallel entries, and removal of incoming and outgoing edges.

Practice problems

Practice walkthrough

Two Sum in Kotlin: all index pairs with nullable map lookups

Solve all-pairs Two Sum with Kotlin HashMap, safe nullable lookups, widened complement arithmetic, and an Array<IntArray> result.

Practice walkthrough

Valid brackets in Kotlin: ArrayDeque and matching delimiters

Use Kotlin ArrayDeque<Char> to match brackets in order. Guard removal, ignore non-bracket text, and test empty input and deep nesting.

Practice walkthrough

Product of array except self in Kotlin: LongArray and constant working state

Use LongArray to compute prefix and suffix products in Kotlin without division. Handle zeros, range boundaries, overflow assumptions, and content comparison.

Practice walkthrough

Counting sort for ages in Kotlin: worked solution

Sort bounded ages with a frequency table, preserving duplicate values. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Best stock profit in Kotlin: one transaction

Find the best single chronological stock trade with one scan. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Most frequent email in Kotlin: hash-map solution

Count email occurrences and return the unique most frequent address. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

First Bad Version in Kotlin: boundary binary search

Find the first true value in a monotone version predicate. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Reverse a linked list in Kotlin: pointer walkthrough

Reverse a linked list by moving nodes from an untouched suffix to a reversed prefix. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Remove kth node from end in Kotlin: zero-based deletion

Delete the zero-based kth node from the end of a linked list. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Merge sorted linked lists in Kotlin: Combine Blockchains

Merge two sorted linked chains while preserving duplicate timestamps. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Count Islands in Kotlin: DFS flood-fill walkthrough

Count side-connected land components with a destructive flood fill of the working grid. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Validate a binary search tree in Kotlin: inherited bounds

Validate every binary-tree node against bounds inherited from all its ancestors. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Binary tree maximum depth in Kotlin: count edges

Measure a binary tree’s longest root-to-leaf path in edges. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Lowest common ancestor in Kotlin: return the original node

Find the lowest common ancestor in a general binary tree and return the original node. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Generate string permutations in Kotlin: positional results

Generate positional string permutations, retaining repeated results for repeated characters. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Group palindrome permutations in Kotlin: counts and keys

Filter palindrome permutations, then group retained strings by character multiset. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Merge intervals in Kotlin: Interview Scheduler

Merge sorted interval coverage with an explicit touching-endpoint rule. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Min-max stack in Kotlin: constant-time extrema

Support stack extrema through histories that retain duplicate record values. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

BFS shortest path in Kotlin: Pilot a Rocket

Find an unweighted shortest route and reconstruct it from predecessor links. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Maze reachability in Kotlin: memoized down/right search

Memoize directed maze reachability while keeping unknown distinct from false. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Search a rotated sorted array in Kotlin: Shifted Search

Search a distinct rotated sorted array by identifying an ordered half. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Search a sorted matrix in Kotlin: staircase walkthrough

Eliminate a row or column at each step in a row-and-column sorted matrix. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

House Robber in Kotlin: maximum nonadjacent sum

Maximize a nonadjacent selection with two rolling prefix optima. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Sum decimal digits in Kotlin: recursive solution

Sum decimal digits through a decreasing recursive integer decomposition. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Count 1, 2, or 3-step paths in Kotlin: Three Moves

Count ordered one-, two-, and three-step routes with rolling dynamic programming. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Coin change in Kotlin: minimum-coin dynamic programming

Minimize unlimited coin usage with a bottom-up amount table. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Validate Sudoku in Kotlin: rows, columns, and subsquares

Validate filled Sudoku entries against rows, columns, and subsquares. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Spiral matrix traversal in Kotlin: Walk a Matrix

Traverse a rectangle in clockwise spiral order using shrinking boundaries. Follow the Kotlin implementation, its correctness argument, worked example, complexity, and boundary tests.