Phase 1 · Foundations · Week 1

Big-O & Arrays

Everything in coding interviews is built on two ideas: how to measure the cost of code (Big-O), and the data structure you'll use in 80% of problems (the array). Nail these in week one and every later pattern has something solid to stand on. No prior CS needed — we start from zero.

🧠 2 concepts + 4 problems 🎮 2 interactive explainers ✅ Self-scoring quiz 📦 100% offline
🎯 By the end of this week you can…

Look at a loop and instantly say "that's O(n)" (and defend it); explain why reading an array index is free but inserting at the front is expensive; and solve easy array problems in a single pass while narrating the time and space cost out loud — exactly what an interviewer is listening for.

0How to use this week

Reading is not learning. This page is built to be used, not skimmed.

  • Play every interactive. The two widgets below aren't decoration — clicking them is how the intuition sticks.
  • Try each problem before revealing. Spend five real minutes stuck. Being stuck and then seeing the trick is worth ten passive read-throughs.
  • Say the complexity out loud. Every time you finish a solution, finish the sentence: "…so that's O(?) time and O(?) space." The interview tests whether you can say it.
  • Then go practise. The lesson teaches the model; spend the rest of the week's hours doing easy array problems until the patterns are automatic.

1Big-O — the cost of code

Big-O answers one question: as the input grows, how fast does the work grow?

It is not a stopwatch. "My laptop runs it in 3 milliseconds" tells an interviewer nothing — a faster machine changes that number, but it doesn't change whether your algorithm scales. Big-O throws away the hardware and the constants and keeps only the shape of the growth. That shape is the difference between code that handles a billion users and code that melts at a thousand.

💡 The one mental move

Ask: "if the input doubles, what happens to the work?" Stays the same → O(1). Doubles too → O(n). Quadruples → O(n²). Goes up by a fixed step → O(log n). That single question classifies almost everything.

Reading complexity straight off the code

You don't compute Big-O with math — you read it off the structure. The loops tell you almost everything:

python count the operations as a function of n = len(nums)
# O(1) — constant. No loop over the input; one step regardless of size.
x = nums[0]

# O(n) — linear. One pass: work grows in lockstep with n.
for v in nums:
    total += v

# O(n^2) — quadratic. A loop inside a loop over the same input.
for i in nums:
    for j in nums:
        print(i, j)

# O(log n) — logarithmic. The input is HALVED each step (n -> n/2 -> n/4 ...).
while n > 1:
    n = n // 2

The complexity ladder — feel the difference

These shapes look similar near the origin and then diverge violently. Toggle each curve and read what it actually costs at n = one million — the gap between O(n) and O(n²) is the whole game.

Big-ONameOps at n = 1,000,000Where you meet it
O(1)constant1Array index, hash lookup, arithmetic
O(log n)logarithmic~20Binary search (Week 5)
O(n)linear1,000,000A single loop — your usual target
O(n log n)linearithmic~20,000,000Good sorting (sorted())
O(n²)quadratic1,000,000,000,000Nested loops — optimise this away
O(2ⁿ)exponentialtoo many to writeBrute-force "try every subset" (Week 6)

Two rules that simplify everything

  • Drop the constants. O(2n + 5) is just O(n). Big-O cares about growth shape, not exact counts — doubling the work doesn't change how it scales.
  • Keep only the biggest term. O(n² + n) is O(n²). For large n, the n² utterly dominates the n, so the small term is noise.

Space complexity — the same idea, for memory

Big-O also measures extra memory your algorithm allocates as the input grows (not counting the input itself). Solving a problem with a couple of variables is O(1) space; building a new list or a hash set of size n is O(n) space. Interviewers grade both — the best answers state the time/space trade-off you chose.

🗣️ Say it like this

"I loop through the array once, doing constant work each step, so it's O(n) time. I only keep a couple of variables, so O(1) space. I could trade space for speed with a hash set — O(n) space — but here one pass is enough."

Quick drill — classify the complexity

Pick the tightest Big-O. (Answers reveal instantly, with the why.)

What is the time complexity? drill
for v in nums:
    if v == target:
        return True
return False
A single pass over n elements, constant work each step → O(n). (It returns early on a match, but Big-O describes the worst case, where the target is absent or last.)
And this one? drill
for i in range(len(nums)):
    for j in range(len(nums)):
        if nums[i] + nums[j] == target:
            return (i, j)
A loop nested inside a loop, each over all n elements → roughly n × n = O(n²). This is the classic "brute force" you'll learn to replace with a hash map next week.
The input shrinks by half each step. Complexity? drill
while n > 1:
    n = n // 2
    steps += 1
Halving repeatedly means you reach 1 in about log₂(n) steps — a million → ~20 steps. Whenever you see the size halve, think O(log n).
Is O(3n + 100) in the same class as O(n)? drill
Yes. Big-O ignores constant multipliers and lower-order terms because they don't affect how the work scales. O(3n + 100), O(n/2), and O(n + 7) are all simply O(n).

2Arrays — the workhorse data structure

An array is a row of values laid out back-to-back in memory. That one physical fact explains all of its speed and all of its slowness.

Because the elements are contiguous and all the same size, the computer can find element i with pure arithmetic: address = start + i × element_size. No searching. That's why reading or writing by index is O(1) — instant, at any size. But it's also why inserting or deleting in the middle is O(n): to make room (or close a gap) every element after the spot has to physically shuffle over.

See it — why index is free but insert-at-front isn't

Each box is a memory slot with its address. Try the three operations and watch the cost.

💡 The takeaway

Index = O(1). Append to the end = O(1)* (amortised). Insert/delete at the front or middle = O(n). If a problem makes you repeatedly insert at the front, an array is the wrong tool — that's a hint to reach for a different structure later (a deque, a linked list).

* "Amortised O(1)": a dynamic array (Python list, Swift Array, Java ArrayList) occasionally runs out of room and copies everything to a bigger block — an O(n) hiccup. But that's rare enough that averaged over many appends, each one is effectively O(1). You can say "amortised constant" and sound exactly right.

Strings are arrays too

A string is essentially an array of characters, so the same costs apply: indexing a character is O(1), scanning is O(n). The one gotcha: in many languages (Python, Java, Swift) strings are immutable — every "edit" actually builds a new string. So building a result character-by-character with s = s + c inside a loop is a sneaky O(n²). The fix is to collect pieces in a list and join once at the end.

Operation cost cheat-sheet

OperationCostWhy
Read / write arr[i]O(1)Direct address arithmetic — no searching
Append to endO(1)*Write the next free slot (amortised)
Insert / delete at front or middleO(n)Every later element must shift
Search for a value (unsorted)O(n)Must check each element
Search for a value (sorted)O(log n)Binary search — Week 5
Scan the whole arrayO(n)One pass

Three array techniques you'll use constantly

  • One pass + a running value. Sweep once, carrying a "best/min/sum so far". Turns many O(n²) brute forces into O(n). (Problems 1 & 2 below.)
  • Two pointers / a write pointer. Use indices that move through the array to rearrange it in place — O(1) extra space. (Problem 3.)
  • Remember what you've seen. Store seen values in a hash set for O(1) "have I seen this?" checks — trading space for speed. (Problem 4 — and all of next week.)

3Worked problems

The fun part. Read the problem, actually try it for a few minutes, peek at the hint if stuck, then check the solution.

1

Best Time to Buy and Sell Stock

easy

prices[i] is the stock price on day i. Buy on one day and sell on a later day to maximise profit. Return the max profit, or 0 if no profit is possible.

Example: [7, 1, 5, 3, 6, 4] → 5  (buy at 1, sell at 6).

The brute force tries every (buy, sell) pair — O(n²). To do it in one pass: as you walk left to right, what's the only thing about the past you need to remember to know your best possible profit today?

Track the cheapest price seen so far. On each day, the best sale today is price − cheapest_so_far. Keep the running maximum of that. One pass, two variables.

python
def max_profit(prices):
    min_price = float('inf')   # cheapest day we could have bought
    best = 0                   # best profit found so far
    for p in prices:
        min_price = min(min_price, p)     # update cheapest buy
        best = max(best, p - min_price)   # best sale if we sell today
    return best
📊 Complexity & pattern

O(n) time (one pass), O(1) space (two scalars). Pattern: one pass + a running value — here, "track the best so far". This single idea retires a whole shelf of O(n²) brute forces.

2

Maximum Subarray (Kadane's algorithm)

easy–med

Find the contiguous subarray with the largest sum, and return that sum.

Example: [-2, 1, -3, 4, -1, 2, 1, -5, 4] → 6  (the subarray [4, -1, 2, 1]).

Walk left to right keeping a "current running sum". At each element you face one decision: is it better to extend the current run, or throw it away and start fresh at this element? When does carrying the past hurt you?

If the running sum ever goes negative, it can only drag down whatever comes next — so drop it and restart at the current element. Track the best sum seen along the way.

python
def max_subarray(nums):
    best = cur = nums[0]
    for x in nums[1:]:
        cur = max(x, cur + x)   # extend the run, or start over at x
        best = max(best, cur)   # remember the best run we've seen
    return best
📊 Complexity & pattern

O(n) time, O(1) space. This is "Kadane's algorithm" — your first taste of dynamic programming (Week 11): the answer ending at each position is built from the answer at the previous one. The naive "sum every subarray" approach is O(n²) or worse.

3

Move Zeroes

easy

Move all 0s to the end while keeping the order of the non-zero elements. Do it in place (no new array).

Example: [0, 1, 0, 3, 12] → [1, 3, 12, 0, 0].

Use a second index — a "write pointer" — that marks where the next non-zero value should go. Sweep with a read pointer; every time you find a non-zero, write it at the write pointer and advance it. What's left to do once the sweep finishes?

Compact all the non-zeros to the front with a write pointer, then fill the remaining slots with zeros.

python
def move_zeroes(nums):
    w = 0                      # next position to write a non-zero
    for x in nums:             # read pointer sweeps the array
        if x != 0:
            nums[w] = x
            w += 1
    while w < len(nums):       # fill the tail with zeros
        nums[w] = 0
        w += 1
📊 Complexity & pattern

O(n) time, O(1) space. Pattern: read pointer + write pointer — the foundation of the "two pointers" family you'll meet properly in Week 3. "In place" is the signal to reach for pointers instead of a new array.

4

Contains Duplicate

easy

Return True if any value appears at least twice, else False.

Example: [1, 2, 3, 1] → True  ·  [1, 2, 3, 4] → False.

The brute force compares every pair — O(n²). One option: sort first (O(n log n)), then duplicates sit next to each other. But can you do it in one pass if you're allowed to remember what you've already seen?

Keep a set of values you've seen. For each element, if it's already in the set you've found a duplicate; otherwise add it. Membership tests on a set are O(1).

python
def contains_duplicate(nums):
    seen = set()
    for x in nums:
        if x in seen:        # O(1) membership check
            return True
        seen.add(x)
    return False
📊 Complexity & pattern

O(n) time, O(n) space — we traded memory for speed. Pattern: hashing. This is the single most important trick in the whole interview, and it's all of Week 2. The sorting approach (O(n log n) time, O(1) extra space) is a great "here's the trade-off" line to mention.

4Self-check quiz

Five questions. If you can ace these, the week's concepts are yours. The score tracks at the bottom.

You frequently read random elements of an array by index. What's the cost per read?
Indexing is O(1): the element's address is computed directly from the start address and the index. No scanning — that's the array's superpower.
You insert a new element at the front of an array of n items. Cost?
O(n). To make room at index 0, all n existing elements must physically move one slot over. Front/middle inserts are the array's weakness.
You repeatedly need to check "have I seen this value before?" across a stream of items. Best tool?
A hash set gives O(1) membership tests, turning a repeated O(n) scan into O(1). Trading O(n) space for speed is the core move of Week 2.
What is the space complexity of the in-place Move Zeroes solution?
O(1) extra space. "In place" means we rearrange the input itself using only a couple of index variables — no new array allocated.
Kadane's algorithm for Maximum Subarray runs in…
O(n). Kadane sweeps once, carrying a running sum and resetting it when it goes negative. The brute force of summing every subarray is O(n²).

5You've finished Week 1 when you can…

Tick these off honestly. They're the bar for moving on — and they save in your browser.

  • Look at any loop and state its Big-O, and justify it in one sentence.
  • Explain the difference between O(n) and O(n²) using the "if the input doubles…" test.
  • Say why array indexing is O(1) but inserting at the front is O(n).
  • Explain "amortised O(1)" for appending to a dynamic array.
  • Solve an easy array problem in one pass and state both its time and space complexity out loud.
  • Score 5/5 on the quiz above without peeking.
  • Spend the rest of the week's hours grinding easy array/string problems until the one-pass pattern is automatic.
🔭 Where this is heading

Notice how Problem 4 itched for something faster than scanning or sorting? That "remember what I've seen in O(1)" instinct is the hash map — and it's the highest-leverage trick in the entire interview. That's Week 2.