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

OperationTime ComplexityWhy?
Access by indexO(1)Jump directly to memory location
Search (unsorted)O(n)Must check each element
Insert at endO(1) amortizedAppend to the end
Insert at indexO(n)Shift elements to make room
Delete at indexO(n)Shift elements to fill gap
Delete at endO(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 StringBuilder for modifications.
  • Python: Strings are immutable. Convert to a list to modify, then join back.
  • Go: Strings are immutable. Convert to []byte or []rune to 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:

  1. Left and right pointers — start at opposite ends, move toward the center
  2. 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:

  1. Can you handle indices correctly? Off-by-one errors are the most common bug
  2. Do you understand time complexity? A brute force O(n^2) solution is fine to start. But can you optimize to O(n)?
  3. Can you use the two-pointer technique? This pattern appears in dozens of problems
  4. Do you handle edge cases? Empty arrays, single elements, duplicates

Practice Problems

Try these problems on LeetCode to practice what you learned:

  1. Two Sum (LeetCode #1) — use a hash map for unsorted arrays (we cover this in DSA Tutorial #4)
  2. Valid Palindrome (LeetCode #125) — skip non-alphanumeric characters
  3. Valid Anagram (LeetCode #242) — character frequency counting
  4. Reverse String (LeetCode #344) — in-place two-pointer approach
  5. 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