DSA · Arrays
n boxes in a row, numbered from 0
An array is one unbroken block of memory holding n values of the same type. You will see why a[i] costs the same whether i is 0 or 999999, walk a single left-to-right traversal over 5 1 4 2 3, and kill the two bugs every fresher writes: seeding the answer with a value the array does not contain, and letting i run all the way to n.
Walk the index across 5 1 4 2 3 →01 The idea
Why a[i] is instant
An array is one unbroken run of memory holding n values of the same type, laid back to back with nothing in between. Because every value takes the same number of bytes, the machine does not have to hunt for a[i] — it computes where the cell lives. The address is the start of the block plus i times the size of one element. One multiplication, one addition, one read.
That is why a[0] and a[999999] cost exactly the same. It is also why counting starts at 0 rather than 1: index 0 means zero elements past the start, which is the first element itself. The last legal index is therefore n − 1, and the one-cell gap between n − 1 and n is where most first-year array bugs live.
02 Worked example
Finding the largest of 5 1 4 2 3
Five values, 5 1 4 2 3, and one rule: read each cell once and hold on to the biggest value seen so far. The variable holding it is best. It is seeded with a[0] rather than with 0, so it is always a value that really exists in the array. On each row the label on the left is the current value of i, the rose box is the cell being read, and the emerald box is the cell best came from.
best was already correct after the seed row and never changed again. That is not something you can rely on. Until the last cell has been read, the value that beats 5 could always be the next one — so the walk cannot stop early, and it costs n reads every single time. Notice also that nothing in those rows is specific to "largest": read one cell, fold it into a variable, move on. Change the variable and change the fold, and the same walk computes the sum or counts the even numbers.
03 The code
The traversal skeleton, and the two bugs
Both loops below try to find the maximum of 5 1 4 2 3. The left one gets it wrong twice over, and on this particular input it may well still print 5: the seed bug is invisible because every value is positive, and the out-of-bounds read only changes the answer if the bytes sitting past the block happen to be bigger than 5. That is exactly why the bugs survive into submitted code — they are not reliably wrong, they are unreliably right. The right one is the skeleton you will reuse for the rest of the course: seed an accumulator, walk from the first legal index to the last, fold each cell in, report.
Broken — the two classic bugs
best = 0 # bug 1: 0 is not in the arrayfor i = 0; i <= n; i++ # bug 2: i reaches n if a[i] > best best = a[i]print best
Correct — the traversal skeleton
best = a[0] # seed from inside the arrayfor i = 1; i < n; i++ # last legal index is n - 1 if a[i] > best best = a[i]print best
Why seed with a[0] and not with 0? Seeding with 0 happens to work on 5 1 4 2 3 because every value is positive. Hand the same loop −9 −3 −7 and it answers 0, a number that is not in the array at all. Seeding from a[0] can only ever return a real element. The price is that the array must be non-empty, so check n > 0 before you start.
Why i < n and never i <= n? The n cells are numbered 0 to n − 1, so i = n is one cell past the end. With n = 5 the broken loop runs i = 0, 1, 2, 3, 4, 5 — six iterations over five cells. This is the size-versus-size-minus-one confusion, and it is the single most common array bug in a written test. Say the loop out loud as "i goes up to but not including n" and the off-by-one stops happening.
Why does the correct version start at i = 1? Because a[0] has already been folded in by the seed. Starting at 0 would compare a[0] with itself, which is harmless for a maximum but is a real bug the moment the body adds instead of compares. The seed decides where i starts — that pairing is the thing to memorise.
Nothing in that skeleton mentions "largest". The walk, the guard and the index arithmetic are fixed; only the seed and the body change. Here are three different jobs done by the same five rows of walking, all over 5 1 4 2 3.
# one walk, three bodies — over 5 1 4 2 3seed sum = 0 i from 0 sum = sum + a[i] → 15seed evens = 0 i from 0 if a[i] % 2 == 0: evens++ → 2seed best = a[0] i from 1 if a[i] > best: best = a[i] → 5
Why do two of them start at 0 and one at 1? The seed again. sum and evens start from 0, a value that is not in the array, so cell 0 has not been counted yet and the walk must read it. best starts from a[0], which has been counted, so the walk begins at the next cell. Get this backwards and your sum quietly drops the first element or adds it twice — and the total still looks plausible, so nobody notices.
05 Cheat sheet
What an array costs
| Operation | Cost | Why |
|---|---|---|
| Read or write a[i] | O(1) | One multiplication and one addition on the base address. The value of i does not matter: a[0] and a[999999] cost the same. |
| Traverse all n cells | O(n) | Every cell read exactly once. Sum, maximum, count, print — the same walk with a different body. |
| Search for a value, unsorted | O(n) | You have no idea where it is, so the worst case reads every cell before finding it or giving up. |
| Insert or delete in the middle | O(n) | The block has to stay contiguous, so every element after the hole shifts by one. |
| Grow beyond the declared size | O(n) | A fixed array cannot grow. You allocate a bigger block and copy all n values across. |
| Space for n elements | O(n) | n × the size of one element, and nothing else. No per-element pointer or header. |
06 Where & why
When the container should be an array
An array is the cheapest container there is — no pointers, no hashing, nothing but a block of memory and some arithmetic. What you pay for that is rigidity. The size is decided up front, and anything that changes the middle costs a full O(n) shuffle.
Reach for it when…
Use something else when…
| Situation | Better choice | Why |
|---|---|---|
| Items arrive and leave at the front constantly | Linked list, or a deque | Shifting n cells on every insert is O(n). A linked list splices a node in at O(1) once you are holding it. |
| You look items up by name, not by position | Hash map / dictionary | Scanning an array for a key is O(n). A hash map answers in O(1) on average and does not care about order. |
| The count is unknown and can keep growing | Dynamic array — vector, ArrayList, Python list | Still an array underneath, but it reallocates and copies for you, which makes appending O(1) amortised. |
| You need the smallest or largest item repeatedly | Heap / priority queue | Re-walking the array is O(n) per query. A heap hands you the extreme in O(1) and removes it in O(log n). |
07 Interview questions
What interviewers actually ask
Eleven questions in the order an interview escalates: what an array is, why the index starts at 0, what happens at a[n], and then the container comparison that decides the round.
What is an array?
Why does the index start at 0 and not 1?
Why is reading a[i] O(1) while reaching the i-th node of a linked list is O(i)?
For an array of size n, what is the last valid index, and what happens if you read a[n]?
What is the most common bug you see in array loops?
Why is inserting into the middle of an array O(n)?
How do you find the maximum of an array, and why can you not stop early?
Someone seeds best with 0 instead of a[0]. Give me an input that breaks it.
Array or linked list — how do you choose?
Is a Python list or a Java ArrayList the same thing as a C array?
You need the sum, the maximum and the count of even numbers. How many passes?
08 Practice problems
Six problems, one traversal each
Every one is the same skeleton: seed something, walk the indices, fold each cell in. Most are a single left-to-right pass; a couple need a second index or a second array, and the twist tells you which. Nothing here needs sorting, recursion, or a hash map.