LearnAbout

Arrays & Strings Fundamentals

On this page
Quick reference
Index read / writeO(1)
Append (at the end)O(1) amortized
Insert / delete at an arbitrary indexO(n)
Unsorted search (find a value)O(n)
String concatenation, naive (in a loop)O(nΒ²) total
String concatenation, via bufferO(n) total
The Idea

An array is a row of numbered lockers sitting side by side in memory, each one exactly the same size. Because every locker is the same size and they're all lined up in order, the computer never has to search for locker #47 β€” it computes the address directly: base address + 47 Γ— (size of one locker). That single multiplication and addition is why reading arr[47] costs the same whether the array has 100 elements or 100 million. Contrast that with the Linked List (Phase 2): a linked list is a scavenger hunt where each stop only tells you where the next one is, so reaching the 47th node means walking through 46 stops first. Arrays trade that walk for a strict requirement β€” every element has to be the same size and sit in one contiguous block β€” in exchange for instant jump-to-anywhere access.

A string is, underneath, an array of characters β€” same contiguous block, same index-to-address arithmetic. But strings carry a wrinkle many languages bake in: immutability. In Python, Java, and JavaScript, a string value can't be changed in place once created β€” "editing" a string by appending or replacing a character doesn't modify the original locker row, it builds an entirely new one and points your variable at that instead. That's invisible when you write it once; it matters a lot the moment it happens inside a loop.

The core trade to memorize: arrays give O(1) access to any index, because the address is pure arithmetic β€” no searching required. Everything else about how arrays perform (appending, inserting, deleting) follows from that same contiguous-block requirement, sometimes helping, sometimes costing you.
Build It
Structures
structure Array:
    data          // one contiguous block of memory, one slot per index
    length        // how many slots currently hold real elements
    capacity      // how many slots are actually allocated (>= length)

// address(i) = base_address + i * element_size
// pure arithmetic, no searching β€” this formula is WHY index access is O(1)
read / write by index β€” O(1)
function readAt(arr, i):
    return arr.data[i]              // one address computation, one memory access

function writeAt(arr, i, value):
    arr.data[i] = value             // same arithmetic, just a write instead of a read
Both directions of the address formula cost the same β€” reading and writing by index are O(1) for the exact same reason.
append β€” O(1) amortized
function append(arr, value):
    if arr.length == arr.capacity:       // no room left in the current block
        newCapacity = arr.capacity * 2    // double it, don't just add one slot
        newData = allocate(newCapacity)
        copy arr.data into newData         // O(n) β€” every existing element must move
        arr.data = newData
        arr.capacity = newCapacity
    arr.data[arr.length] = value
    arr.length += 1
Most appends are O(1) β€” there's room, so it's just a write. Occasionally the block is full and a resize copies every element, an O(n) event. "Amortized" means: spread that occasional O(n) cost evenly across all the O(1) appends that led up to it, and the average cost per append still comes out to O(1). Doubling the capacity (not adding a fixed amount) is what makes the math work β€” each resize buys enough headroom that resizes get exponentially rarer as the array grows.
insert / delete at an arbitrary index β€” O(n)
function insertAt(arr, i, value):
    // everything from i onward has to slide one slot right first, to open a gap β€”
    // there's no way to make room without physically moving them
    for j from arr.length down to i + 1:
        arr.data[j] = arr.data[j - 1]
    arr.data[i] = value
    arr.length += 1

function deleteAt(arr, i):
    // mirror image: everything after i slides one slot left to close the gap
    for j from i to arr.length - 2:
        arr.data[j] = arr.data[j + 1]
    arr.length -= 1
Insert-at-front or delete-at-front is the worst case β€” nearly the whole array shifts. Insert/delete at the very end is the cheap case (nothing to shift), which is exactly the append special case above.
building a string β€” naive concatenation vs. a buffer
// naive: looks like one simple loop, but each += silently builds an entire NEW string
function buildNaive(words):
    result = ""
    for w in words:
        result = result + w    // copies the ENTIRE result so far, every single time
    return result

// buffer: collect pieces in a resizable array (append is O(1) amortized, see above),
// then pay for exactly one real string-build at the very end
function buildWithBuffer(words):
    buffer = []
    for w in words:
        buffer.append(w)        // O(1) amortized per piece
    return join(buffer)         // one O(n) pass over the total output length
Because strings are immutable, result + w can't extend result in place β€” it allocates a brand new string the length of everything so far, copies the old content in, then adds w. Do that n times and you're copying 1 + 2 + ... + n characters total, which is O(nΒ²) β€” the exact "hidden O(n) inside a loop" pitfall from Big-O Notation, wearing a string costume.
Know It

Two questions decide the cost: is the position of the work fixed (an index you already know) or does it depend on searching through or shifting other elements? Fixed-position work is O(1); anything that has to look at or move other elements scales with n.

OperationTimeSpaceWhy
Index read / writeO(1)O(1)Pure address arithmetic β€” no dependence on how many elements exist elsewhere in the array.
Append (at the end)O(1) amortizedO(1) amortizedUsually a direct write. The occasional O(n) resize-and-copy is spread across enough prior appends that the average stays O(1).
Insert / delete at an arbitrary indexO(n)O(1)Every element after the target index has to shift one slot to open or close the gap β€” worst case (index 0) shifts almost the whole array.
Unsorted search (find a value)O(n)O(1)No shortcut to a value's position β€” elements are checked one at a time until found or exhausted.
String concatenation, naive (in a loop)O(nΒ²) totalO(nΒ²) total allocatedEach += copies everything accumulated so far because strings are immutable β€” n concatenations copy roughly 1 + 2 + ... + n characters.
String concatenation, via bufferO(n) totalO(n)Collecting pieces in a resizable array costs O(1) amortized per piece; joining once at the end is a single O(n) pass.
Break It

Empty array

the array has zero elements

arr[0] on an empty array isn't "the first element, whatever that is" β€” there is no first element. Reading, searching, or looping over an empty array has to be a valid no-op, not a crash; code that assumes "there's always at least one element" breaks on the smallest legal input.

Off-by-one at the boundary

an index equals length instead of length βˆ’ 1

The last valid index of an array with length n is n βˆ’ 1, not n. Loop conditions like i <= length, or hand-written boundary math (a midpoint, a window edge, a last-common-index check like in Longest Common Prefix), are the classic source of an out-of-bounds read or write exactly one past the real end.

Mutating an immutable string in place

code tries to change a character of a string directly

In languages where strings are immutable, something like s[0] = 'x' either throws an error or silently does nothing useful, because there is no "in place" β€” every apparent edit produces a new string object. Code that assumes a string behaves like a mutable character array (as arrays do) either crashes or quietly keeps operating on a stale copy.

Modifying an array while iterating over it

elements are inserted or removed during a loop over the same array

Deleting index i while a loop's cursor is also at i shifts every later element down one slot β€” the loop's next step then skips whatever slid into the spot it just left, silently missing an element. The safe pattern is to iterate backward when deleting, or build a fresh array of what should remain instead of mutating mid-walk.

Use It
Remove Element
Overwrite in place: walk through once with a separate "write position" that only advances when the current element should be kept β€” no shifting the whole array, no extra array.
Easy
Merge Sorted Array
nums1 has extra empty space at the end β€” fill it from the back. Compare the largest remaining values of both arrays and place the bigger one in the last open slot, working backward so you never overwrite a value you haven't read yet.
Easy
Plus One
Walk from the rightmost digit backward, carrying a 9 β†’ 0 rollover into the next digit. If every digit was a 9, the number grows a new leading digit β€” a case a fixed-size in-place write can't handle, so that one case needs a brand new, longer array.
Easy
Rotate Array
Reversing sub-ranges three times rotates an array in place with no second array: reverse the whole array, then reverse the first k elements, then reverse the rest.
Medium
Longest Common Prefix
Compare characters column by column across every string at the same index, and stop the instant two characters disagree β€” or you run off the end of the shortest string, whichever comes first.
Easy
String to Integer (atoi)
Walk the string once, building the number digit by digit the same way base-10 place value works (result = result * 10 + digit). The parsing itself is easy β€” the fiddly part is the boundary conditions: leading whitespace, an optional sign, stopping at the first non-digit, and clamping the result to the 32-bit range.
Medium
Majority Element
You already know the O(n) shape from Big-O Notation β€” now actually write it: track a running candidate and a count as you scan once left to right, incrementing when you see the candidate and decrementing (then swapping candidates) otherwise. No sorting, no nested loop.
Easy
Best Time to Buy and Sell Stock
The same O(n) single-pass idea from Big-O Notation β€” track the lowest price seen so far as you walk the array once, and at each day check the profit if you sold today against the best profit seen so far.
Easy
Zigzag Conversion
Simulate writing characters into numRows separate buffers, moving a "current row" index down and then back up in a zigzag as you place each character β€” then concatenate the row buffers. No actual 2D grid needed.
Medium
Valid Palindrome
Compare the string from both ends inward, one step at a time, skipping non-alphanumeric characters and ignoring case, until the two scanning positions meet in the middle.
Easy