Picture a hotel hallway. Every room is exactly the same width — 4 meters, let's say — and numbered sequentially starting from 0. If you're standing at the front desk and someone asks you to deliver a package to room 417, you don't need to walk past every door counting up from 0. You just compute: the hallway starts at position 0, each room is 4 meters wide, so room 417 starts at 0 + 417 * 4 = 1668 meters down the hall. One multiplication, one addition, and you're there.
Arrays work exactly this way. When your program creates an array, the operating system hands it a contiguous block of memory — a straight run of same-sized slots, one after another, with no gaps. The computer knows the address where slot 0 starts. It knows how wide each slot is (say, 8 bytes for a 64-bit number). To find slot i, it computes startAddress + i * slotSize. That is the entire algorithm. One multiplication, one addition, done.
This is O(1) access — constant time. The formula does not care how big the array is. Whether it holds 8 elements or 8 billion, the arithmetic is exactly the same: one multiply, one add, one memory fetch. There is no loop, no scanning, no “it depends.” The cost is flat.
But notice what makes the formula work: every slot is the same size. That is the key constraint. If room 217 were a suite twice as wide as a standard room, the simple offset math would break — you'd need to walk the hallway measuring doors to find the right one. Arrays avoid this by storing fixed-width values in a fixed-width grid. Uniformity is what buys you speed.
An array has 10,000 elements. How many operations does it take to access element at index 7,391?
Every index you tried — whether it was 0, 3, or 7 — took exactly one operation. The computer didn't start at the beginning and count forward. It computed the address and jumped. That is the superpower of arrays: constant-time random access. An array of ten thousand elements is no harder to index into than an array of three.
But this superpower has a catch, and it's a subtle one. The formula maps an index to a value. You hand it a position and it gives you back what's stored there. That works beautifully — as long as you already know the position you want.
What happens when you don't?
You know where room 417 is — just compute the offset. But now the question changes. Someone asks: “Which room is Alice staying in?”
The offset formula is useless here. It converts room numbers to occupants, but you need the reverse: given an occupant, find the room number. And for an unsorted guest list, there is no formula for that. So how long does it take? It depends — but on what, exactly? On the size of the haystack? On where the needle happens to sit? On whether the needle is even there at all? Before reading on, get your hands on the system below and feel out the answer yourself.
Drag the array size. Drag the target position — try the front, the end, somewhere in the middle, and slide it past the right edge to make the value disappear entirely. Then run the scan and watch the checks counter.
SLIDE PAST 15 TO MAKE TARGET ABSENT
function indexOf(arr, target) { for (let i = 0; i < arr.length; i++) { if (arr[i] === target) return i } // Exhausted — target is not present return -1}What you just felt is linear search, and it's the only general-purpose search you can do on an unsorted array. You start at index 0 and check each element, one at a time, until you either find what you're looking for or reach the end. The cost grows proportionally with the array length — O(n). Best case is one check. Worst case (and the case you can never rule out in advance) is every cell.
Every time you write arr.includes(x) or arr.indexOf(x), the engine runs this exact loop under the hood. If the value is at the end, it checks every element. If the value isn't in the array at all, it still checks every element — it can't know the value is absent until it has looked everywhere.
Now consider what happens when you nest this inside another loop. Say you have two arrays and you want to find common elements: for each element in array A, check if it exists in array B using .includes(). The outer loop is O(n). The inner .includes() is O(n). Together, that's O(n^2) — and you might not even realize it, because the code looks like two clean one-liners.
The number of cells you had to check wasn't fixed — it depended on where the target happened to sit. There was no shortcut, no way to skip ahead, because an unsorted array gives you no information about where a particular value might live. Every cell is equally likely, so you must check them all.
You've now experienced both sides of the array coin, and they feel completely different. One operation — access by index — was instant every time. The other — search by value — made you work for it. These two operations look similar on the surface (both answer “what's at this location?” or “where is this thing?”), but their costs are worlds apart:
O(1). Jump straight there. The formula handles it.O(n). Check cells until you find it. No formula can help.That gap is the single most important fact about arrays. It's the foundation that every other data structure decision builds on.
An unsorted array has 10,000 elements. You need to check whether the value 99 exists. In the worst case, how many elements do you inspect?
This gap hides behind clean APIs, which makes it dangerous. Consider a few real scenarios:
Duplicate detection. You have an array of usernames and want to check that a new signup isn't already taken. You write usernames.includes(newName) — that's O(n). If you're checking every new signup against the full list, you're paying O(n) per check. Process a batch of 1,000 signups and the total cost is O(n * 1000). The array grows, and so does your pain.
Finding duplicates. You want to know if any element appears twice. The brute-force approach compares every pair: for each element, scan the rest of the array. That's O(n^2), and it comes from the same root cause — searching by value is expensive.
Frequency counting. You need to count how many times each value appears. For every element, you scan the array to count its occurrences. Again, O(n) search inside an O(n) loop gives you O(n^2).
Every one of these problems traces back to the access-search gap. You can get values cheaply, but you can't find them cheaply. The array gives you a fast forward direction (index to value) but no fast reverse direction (value to index).
That reverse direction is exactly what hash maps provide. The next lesson shows how to build a data structure that makes value lookup O(1) — closing the gap entirely.
Access and search aren't the only operations with different costs. Arrays have a whole spectrum of operation prices, and the cost depends almost entirely on where in the array you're touching.
Think of a bookshelf packed tight with books, spine to spine, no gaps. Adding a new book to the right end is trivial — just place it after the last one. But adding a book to the left end? You have to slide every single book one slot to the right to make room. The shelf has 1,000 books? That's 1,000 slides before you can place your new one.
Arrays work the same way. Because they're contiguous in memory — one slot right after the next — inserting at position 0 means physically shifting every existing element one position forward. That's O(n). Inserting at the end, though, just means writing to the next available slot: O(1). Deleting follows the same logic. Remove the last element and there's no cleanup. Remove the first and every remaining element slides down to fill the gap.
This gives arrays a clear personality: they're fast at the tail and expensive at the head. Operations that touch the end — push, pop — are constant time. Operations that touch the beginning — unshift, shift — are linear. Operations in the middle fall somewhere in between, proportional to how many elements need to move.
arr[42]You now have the full cost map for arrays. Access by index is O(1), always. Search by value is O(n). Append and pop at the end are O(1). Insert and delete at the beginning are O(n). This cost profile is what makes arrays perfect for some jobs (random access, stack-like push/pop) and painful for others (searching, frequent insertions at the front).
The big takeaway: search is the expensive operation that shows up everywhere — in .includes(), .indexOf(), .find(), duplicate checks, frequency counts. The next lesson introduces the data structure that eliminates that cost: the hash map.