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.
Educative.io (Collaboration)
Educative.io is my favorite resource for interview preparation and I’ve already recommended their “Grokking the Coding Interview Patterns” course a lot of times before.
1. Sliding Window Pattern
The sliding window pattern is useful for solving problems related to contiguous subarrays, substrings, and sequences.
Example: Maximum Sum Subarray of Size K
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
}
2. Two Pointers Pattern
This pattern is commonly used when dealing with sorted arrays or linked lists to find pairs that match a condition.
Example: Pair Sum in Sorted Array
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
}
3. Fast & Slow Pointers Pattern
This pattern is useful for cycle detection in linked lists and similar structures.
Example: Detecting Cycle in Linked List
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
}
4. Merge Intervals Pattern
This pattern is useful for interval-related problems, such as merging overlapping intervals.
Example: 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
}
5. Backtracking Pattern
Backtracking is useful for problems like permutations, combinations, and solving puzzles.
Example: Generating All Subsets
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
}
6. Dynamic Programming (DP) Pattern
DP is a powerful technique for optimizing recursive solutions by storing intermediate results.
Example: Fibonacci Sequence Using Memoization
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]!!
}
7. Greedy Algorithm Pattern
Greedy algorithms work by making the locally optimal choice at each step.
Example: Minimum Coins for Change
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
}
8. Graph Traversal Pattern
Graph algorithms often use BFS and DFS for traversal and search problems.
Example: BFS Traversal in Graph
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.
Educative.io (Collaboration)
Educative.io is my favorite resource for interview preparation and I’ve already recommended their “Grokking the Coding Interview Patterns” course a lot of times before.
Conclusion
Understanding these patterns will help you write efficient, readable, and optimized Kotlin code. If you're preparing for interviews, mastering these patterns is crucial.
For a deep dive into coding patterns and hands-on exercises, check out “Grokking the Coding Interview Patterns” – a must-have course for Android Kotlin developers!
📢 Don't forget to share this with fellow developers and subscribe to my newsletter for more Kotlin and Android tips! 🚀