As an Android Kotlin developer, understanding coding patterns is essential for writing clean, efficient, and scalable code. If you're preparing for coding interviews or just want to improve your problem-solving skills, mastering these patterns can significantly enhance your approach to problem-solving.
In this blog, we'll explore some fundamental coding patterns in Kotlin with real-world examples. These patterns are crucial for interviews and general software development.
The sliding window pattern is useful for solving problems related to contiguous subarrays, substrings, and sequences.
fun maxSumSubarray(arr: IntArray, k: Int): Int {
var maxSum = 0
var windowSum = 0
var start = 0
for (end in arr.indices) {
windowSum += arr[end]
if (end >= k - 1) {
maxSum = maxOf(maxSum, windowSum)
windowSum -= arr[start]
start++
}
}
return maxSum
}
This pattern is commonly used when dealing with sorted arrays or linked lists to find pairs that match a condition.
fun hasPairWithSum(arr: IntArray, target: Int): Boolean {
var left = 0
var right = arr.size - 1
while (left < right) {
val sum = arr[left] + arr[right]
when {
sum == target -> return true
sum < target -> left++
else -> right--
}
}
return false
}
This pattern is useful for cycle detection in linked lists and similar structures.
class ListNode(val value: Int) {
var next: ListNode? = null
}
fun hasCycle(head: ListNode?): Boolean {
var slow = head
var fast = head
while (fast?.next != null) {
slow = slow?.next
fast = fast.next?.next
if (slow == fast) return true
}
return false
}
This pattern is useful for interval-related problems, such as merging overlapping intervals.
data class Interval(val start: Int, val end: Int)
fun mergeIntervals(intervals: List<Interval>): List<Interval> {
if (intervals.isEmpty()) return emptyList()
val sortedIntervals = intervals.sortedBy { it.start }
val merged = mutableListOf(sortedIntervals[0])
for (i in 1 until sortedIntervals.size) {
val last = merged.last()
val current = sortedIntervals[i]
if (current.start <= last.end) {
merged[merged.lastIndex] = Interval(last.start, maxOf(last.end, current.end))
} else {
merged.add(current)
}
}
return merged
}
Backtracking is useful for problems like permutations, combinations, and solving puzzles.
fun generateSubsets(nums: IntArray): List<List<Int>> {
val result = mutableListOf<List<Int>>()
fun backtrack(start: Int, current: MutableList<Int>) {
result.add(ArrayList(current))
for (i in start until nums.size) {
current.add(nums[i])
backtrack(i + 1, current)
current.removeAt(current.size - 1)
}
}
backtrack(0, mutableListOf())
return result
}
DP is a powerful technique for optimizing recursive solutions by storing intermediate results.
fun fibonacci(n: Int, memo: MutableMap<Int, Int> = mutableMapOf()): Int {
if (n <= 1) return n
if (memo.containsKey(n)) return memo[n]!!
memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo)
return memo[n]!!
}
Greedy algorithms work by making the locally optimal choice at each step.
fun minCoins(coins: IntArray, amount: Int): Int {
coins.sortDescending()
var remaining = amount
var count = 0
for (coin in coins) {
if (remaining == 0) break
count += remaining / coin
remaining %= coin
}
return if (remaining == 0) count else -1
}
Graph algorithms often use BFS and DFS for traversal and search problems.
fun bfs(graph: Map<Int, List<Int>>, start: Int): List<Int> {
val queue = ArrayDeque<Int>()
val visited = mutableSetOf<Int>()
val result = mutableListOf<Int>()
queue.add(start)
visited.add(start)
while (queue.isNotEmpty()) {
val node = queue.removeFirst()
result.add(node)
for (neighbor in graph[node] ?: emptyList()) {
if (neighbor !in visited) {
visited.add(neighbor)
queue.add(neighbor)
}
}
}
return result
}
Here’s a comprehensive list of coding patterns commonly used in problem-solving and coding interviews:
Array & String Patterns
Sliding Window
Two Pointers
Fast & Slow Pointers
Merge Intervals
Kadane’s Algorithm (Maximum Subarray)
Dutch National Flag (Sort Colors)
Cyclic Sort (Find Missing Numbers)
Recursion & Backtracking Patterns
Backtracking (Subset, Permutation, Combination)
Branch & Bound
Divide and Conquer
Dynamic Programming (DP) Patterns
Knapsack (0/1 & Unbounded)
Fibonacci Series (Memoization & Tabulation)
Longest Common Subsequence (LCS)
Palindrome Partitioning
Coin Change / Minimum Steps to Reduce a Number
Greedy Algorithm Patterns
Activity Selection
Huffman Encoding
Interval Scheduling
Job Scheduling with Deadlines
Graph Traversal Patterns
Breadth-First Search (BFS)
Depth-First Search (DFS)
Dijkstra’s Algorithm (Shortest Path)
Bellman-Ford Algorithm
Floyd-Warshall Algorithm
Topological Sorting (Kahn’s Algorithm)
Union-Find (Detect Cycle in Graphs)
Minimum Spanning Tree (Kruskal, Prim’s Algorithm)
Tree & Binary Search Patterns
Binary Search
Binary Search on Answer (Minimize Max Distance, Aggressive Cows)
Inorder, Preorder, Postorder Traversal
Lowest Common Ancestor (LCA)
Trie (Prefix Tree) Usage
Segment Tree / Fenwick Tree (Range Queries)
Heap & Priority Queue Patterns
Top K Elements (Kth Largest, Kth Smallest)
Median of a Stream
Merge K Sorted Lists
Bit Manipulation Patterns
XOR Manipulation (Find Missing Number, Single Non-Duplicate)
Bitmask DP
Matrix Patterns
Spiral Traversal
Flood Fill (DFS/BFS in Grid)
Matrix Exponentiation
This covers most of the major coding patterns used in problem-solving and interviews.
Understanding these patterns will help you write efficient, readable, and optimized Kotlin code. If you're preparing for interviews, mastering these patterns is crucial.
📢 Don't forget to share this with fellow developers and subscribe to my newsletter for more Kotlin and Android tips! 🚀
Akshay Nandwana
Founder AndroidEngineers
You can connect with me on:
Join our upcoming classes
Our Courses