Arrays and strings are the most common data structures in coding interviews. Almost every technical interview at Google, Meta, Amazon, or any tech company starts with an array or string problem. If you want to pass coding interviews, you need to master these two first.
In this article, you will learn what arrays and strings are, their time complexities, and how to solve classic interview problems. We show every example in Kotlin, Python, and Go.
What is an Array?
An array is a collection of elements stored in contiguous memory. This means all elements sit next to each other in memory. Each element has an index — a number that tells you its position. The first element is at index 0.
Think of an array like a row of lockers. Each locker has a number (index) and holds one item (value). You can open any locker instantly if you know its number.
Static vs Dynamic Arrays
Static arrays have a fixed size. You decide the size when you create them. You cannot add more elements than the size allows.
Dynamic arrays can grow and shrink. When the array is full, it creates a bigger array behind the scenes and copies everything over. In Kotlin, this is ArrayList. In Python, this is list. In Go, this is a slice.
For interviews, you almost always use dynamic arrays.
Array Operations and Time Complexity
| Operation | Time Complexity | Why? |
|---|---|---|
| Access by index | O(1) | Jump directly to memory location |
| Search (unsorted) | O(n) | Must check each element |
| Insert at end | O(1) amortized | Append to the end |
| Insert at index | O(n) | Shift elements to make room |
| Delete at index | O(n) | Shift elements to fill gap |
| Delete at end | O(1) | Remove last element |
O(1) means constant time — it takes the same time no matter how big the array is. O(n) means linear time — it takes longer as the array grows.
Basic Array Operations
Here is how to create arrays and perform common operations in all three languages.
Kotlin:
fun main() {
// Create a dynamic array
val numbers = mutableListOf(10, 20, 30, 40, 50)
// Access by index - O(1)
println(numbers[2]) // 30
// Insert at end - O(1)
numbers.add(60)
// Insert at index - O(n)
numbers.add(1, 15) // [10, 15, 20, 30, 40, 50, 60]
// Delete by index - O(n)
numbers.removeAt(3) // removes 30
// Search - O(n)
val index = numbers.indexOf(40) // returns 3
println("Found 40 at index $index")
}
Python:
# Create a dynamic array (list)
numbers = [10, 20, 30, 40, 50]
# Access by index - O(1)
print(numbers[2]) # 30
# Insert at end - O(1)
numbers.append(60)
# Insert at index - O(n)
numbers.insert(1, 15) # [10, 15, 20, 30, 40, 50, 60]
# Delete by index - O(n)
numbers.pop(3) # removes 30
# Search - O(n)
index = numbers.index(40) # returns 3
print(f"Found 40 at index {index}")
Go:
package main
import "fmt"
func main() {
// Create a slice (dynamic array)
numbers := []int{10, 20, 30, 40, 50}
// Access by index - O(1)
fmt.Println(numbers[2]) // 30
// Insert at end - O(1)
numbers = append(numbers, 60)
// Insert at index 1 - O(n)
numbers = append(numbers[:1], append([]int{15}, numbers[1:]...)...)
// Delete by index - O(n)
numbers = append(numbers[:3], numbers[4:]...)
// Search - O(n)
for i, v := range numbers {
if v == 40 {
fmt.Printf("Found 40 at index %d\n", i)
break
}
}
}
What are Strings?
A string is a sequence of characters. You can think of a string as an array of characters. In most languages, strings are immutable — you cannot change a character in place. You must create a new string instead.
- Kotlin: Strings are immutable. Use
StringBuilderfor modifications. - Python: Strings are immutable. Convert to a list to modify, then join back.
- Go: Strings are immutable. Convert to
[]byteor[]runeto modify.
String Reversal
Reversing a string is one of the most common interview warm-up questions. Here is how to do it in all three languages.
Kotlin:
fun reverseString(s: String): String {
return s.reversed()
}
// In-place reversal with a char array
fun reverseInPlace(chars: CharArray) {
var left = 0
var right = chars.size - 1
while (left < right) {
val temp = chars[left]
chars[left] = chars[right]
chars[right] = temp
left++
right--
}
}
Python:
def reverse_string(s: str) -> str:
return s[::-1]
# In-place reversal with a list
def reverse_in_place(chars: list) -> None:
left, right = 0, len(chars) - 1
while left < right:
chars[left], chars[right] = chars[right], chars[left]
left += 1
right -= 1
Go:
func reverseString(s string) string {
runes := []rune(s)
left, right := 0, len(runes)-1
for left < right {
runes[left], runes[right] = runes[right], runes[left]
left++
right--
}
return string(runes)
}
The Two-Pointer Technique
The two-pointer technique is one of the most important patterns for array and string problems. You use two variables (pointers) that move through the array from different positions.
There are two common setups:
- Left and right pointers — start at opposite ends, move toward the center
- Slow and fast pointers — both start at the beginning, move at different speeds
Example: Two Sum (Sorted Array)
Given a sorted array and a target number, find two numbers that add up to the target. Return their indices.
Kotlin:
fun twoSumSorted(numbers: IntArray, target: Int): IntArray {
var left = 0
var right = numbers.size - 1
while (left < right) {
val sum = numbers[left] + numbers[right]
when {
sum == target -> return intArrayOf(left, right)
sum < target -> left++
else -> right--
}
}
return intArrayOf() // no solution found
}
Python:
def two_sum_sorted(numbers: list, target: int) -> list:
left, right = 0, len(numbers) - 1
while left < right:
current_sum = numbers[left] + numbers[right]
if current_sum == target:
return [left, right]
elif current_sum < target:
left += 1
else:
right -= 1
return [] # no solution found
Go:
func twoSumSorted(numbers []int, target int) []int {
left, right := 0, len(numbers)-1
for left < right {
sum := numbers[left] + numbers[right]
if sum == target {
return []int{left, right}
} else if sum < target {
left++
} else {
right--
}
}
return nil // no solution found
}
How it works: Start with one pointer at the beginning and one at the end. If the sum is too small, move the left pointer right (bigger number). If the sum is too big, move the right pointer left (smaller number). This runs in O(n) time with O(1) extra space.
Palindrome Check
A palindrome reads the same forward and backward. For example, “racecar” is a palindrome. This is a classic two-pointer problem.
Kotlin:
fun isPalindrome(s: String): Boolean {
var left = 0
var right = s.length - 1
while (left < right) {
if (s[left] != s[right]) return false
left++
right--
}
return true
}
Python:
def is_palindrome(s: str) -> bool:
left, right = 0, len(s) - 1
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
Go:
func isPalindrome(s string) bool {
runes := []rune(s)
left, right := 0, len(runes)-1
for left < right {
if runes[left] != runes[right] {
return false
}
left++
right--
}
return true
}
Valid Anagram
Two strings are anagrams if they contain the same characters in a different order. For example, “listen” and “silent” are anagrams.
The best approach is to count the frequency of each character. If both strings have the same character counts, they are anagrams.
Kotlin:
fun isAnagram(s: String, t: String): Boolean {
if (s.length != t.length) return false
val count = IntArray(26)
for (i in s.indices) {
count[s[i] - 'a']++
count[t[i] - 'a']--
}
return count.all { it == 0 }
}
Python:
def is_anagram(s: str, t: str) -> bool:
if len(s) != len(t):
return False
count = [0] * 26
for i in range(len(s)):
count[ord(s[i]) - ord('a')] += 1
count[ord(t[i]) - ord('a')] -= 1
return all(c == 0 for c in count)
Go:
func isAnagram(s, t string) bool {
if len(s) != len(t) {
return false
}
var count [26]int
for i := 0; i < len(s); i++ {
count[s[i]-'a']++
count[t[i]-'a']--
}
for _, c := range count {
if c != 0 {
return false
}
}
return true
}
Time complexity: O(n) where n is the length of the strings. Space complexity: O(1) because the count array is always size 26.
Why Interviewers Ask Array and String Questions
Interviewers love arrays and strings because they test several skills at once:
- Can you handle indices correctly? Off-by-one errors are the most common bug
- Do you understand time complexity? A brute force O(n^2) solution is fine to start. But can you optimize to O(n)?
- Can you use the two-pointer technique? This pattern appears in dozens of problems
- Do you handle edge cases? Empty arrays, single elements, duplicates
Practice Problems
Try these problems on LeetCode to practice what you learned:
- Two Sum (LeetCode #1) — use a hash map for unsorted arrays (we cover this in DSA Tutorial #4)
- Valid Palindrome (LeetCode #125) — skip non-alphanumeric characters
- Valid Anagram (LeetCode #242) — character frequency counting
- Reverse String (LeetCode #344) — in-place two-pointer approach
- Contains Duplicate (LeetCode #217) — use a set for O(n) solution
What’s Next?
In the next article, we cover Linked Lists — a data structure where elements are connected through pointers instead of sitting next to each other in memory. You will learn about singly linked lists, doubly linked lists, and the fast-slow pointer technique.
Next: DSA Tutorial #2: Linked Lists — Singly, Doubly, and Circular
Full series: DSA from Zero to Interview Ready