Arrays are the first data structure everyone meets and the one most people never look at closely again. That’s a shame, because almost every structure you’ll learn later (strings, stacks, hash tables, even the guts of a vector database) is built on top of the humble array. This post is the written, go-deeper companion to my 60-second visual explainer:
If you prefer moving pictures, watch it on YouTube and subscribe to @onurthedev — I turn one CS concept into a visual short every week, and each one gets a deep-dive post like this here.
Table of contents
Open Table of contents
The one-sentence definition
An array is a contiguous block of memory holding elements of the same size, where each element is identified by its index.
Two words in that sentence do all the work: contiguous and index.
Why contiguous memory is the whole trick
When you create an array, the runtime reserves one unbroken run of bytes. If an array of 32-bit integers starts at memory address 1000, the layout is:
index: 0 1 2 3 4
address: 1000 1004 1008 1012 1016
value: 42 7 19 3 88
That layout means the computer never searches for an element. It computes where the element must be:
address_of(i) = base_address + i * element_size
address_of(3) = 1000 + 3 * 4 = 1012
One multiplication, one addition, done. It does not matter if the array has ten elements or ten million: fetching arr[3] costs the same. That’s what we mean when we say array access is O(1), constant time. (That formula is also the real reason indexes start at 0, which is the topic of the next post in this series.)
What Python and TypeScript actually give you
Neither language hands you a raw fixed-size C array by default, and it’s worth knowing what you’re really holding:
- A Python
listis a dynamic array of references to objects. It over-allocates room so appends stay fast, and it can hold mixed types because every slot is just a pointer. - A TypeScript/JavaScript array is also a dynamic array under the hood. Modern engines like V8 store it as contiguous memory as long as you keep it dense and single-typed, which is one reason
number[]beats(number | string)[]for hot paths. - When you need true machine-level arrays: Python has
array.arrayand NumPy’sndarray; JavaScript has typed arrays likeFloat64Array.
The mental model of “contiguous slots, indexed from 0” still holds for all of them, and so do the complexities below.
The common operations, with code
Here is the cheat sheet, then the code:
| Operation | Complexity | Why |
|---|---|---|
| Access by index | O(1) | address arithmetic, no searching |
| Update by index | O(1) | same arithmetic, then overwrite |
| Search (unsorted) | O(n) | might have to look at every element |
| Insert at end | O(1) amortized | usually free space; occasional resize is O(n) |
| Insert at index | O(n) | everything after it must shift right |
| Delete at end | O(1) | nothing shifts |
| Delete at index | O(n) | everything after it must shift left |
| Traverse | O(n) | touch each element once |
Access and update: O(1)
scores = [42, 7, 19, 3, 88]
third = scores[2] # 19 — one arithmetic step, regardless of length
scores[2] = 100 # update is the same arithmetic plus a write
last = scores[-1] # 88 — Python nicety: negative indexes count from the end
const scores: number[] = [42, 7, 19, 3, 88];
const third = scores[2]; // 19
scores[2] = 100; // O(1) update
const last = scores.at(-1); // 88 — .at() gives you negative indexing in TS/JS
Search: O(n)
In an unsorted array there is no shortcut. You check elements one by one, and on average you’ll check half of them:
def find_index(items: list[int], target: int) -> int:
for i, value in enumerate(items):
if value == target:
return i
return -1
find_index(scores, 3) # 3
3 in scores # True — same O(n) scan, nicer syntax
function findIndex(items: number[], target: number): number {
for (let i = 0; i < items.length; i++) {
if (items[i] === target) return i;
}
return -1;
}
scores.indexOf(3); // 3 — built-in, still O(n)
scores.includes(3); // true
If the array is sorted, binary search drops this to O(log n). That’s a future episode; the point for now is that sortedness is a property you pay for elsewhere to make search cheap.
Insert: O(1) at the end, O(n) anywhere else
Appending is cheap because dynamic arrays keep spare capacity at the end. Inserting in the middle is expensive because contiguity is non-negotiable: to put something at index 1, every element from index 1 onward has to move one slot right.
scores.append(55) # O(1) amortized
scores.insert(1, 99) # O(n) — shifts everything after index 1
scores.push(55); // O(1) amortized
scores.splice(1, 0, 99); // O(n) — same shifting cost, sneakier syntax
The word amortized hides a nice mechanism: when the reserved block fills up, the runtime allocates a bigger block (typically ~1.5 to 2x) and copies everything over. That one append costs O(n), but it buys so much free space that the average append stays O(1). You get this for free; it’s still worth knowing it happens.
Delete: mirror image of insert
scores.pop() # O(1) — remove last
scores.pop(1) # O(n) — remove index 1, shift everything left
scores.remove(88) # O(n) — search for the value, then shift
scores.pop(); // O(1)
scores.splice(1, 1); // O(n)
A classic interview-grade trick: if order doesn’t matter, delete from the middle in O(1) by swapping the victim with the last element and popping.
def swap_remove(items: list, i: int) -> None:
items[i] = items[-1]
items.pop()
Traverse: O(n)
for score in scores:
print(score)
total = sum(scores)
doubled = [s * 2 for s in scores]
for (const score of scores) {
console.log(score);
}
const total = scores.reduce((acc, s) => acc + s, 0);
const doubled = scores.map(s => s * 2);
There is a lot more to say about iteration (index-based vs value-based loops, off-by-one traps, when for...in betrays you in JavaScript). I cover all of it in the follow-up post.
When an array is the right tool
Reach for an array when you read by position a lot, iterate over everything, or mostly add/remove at the end. Think hard before using one when you insert/delete in the middle constantly (a linked list or a different design may fit) or when you mostly look things up by a key rather than a position (that’s a hash map’s job). Those structures are coming up in this series, and every one of them will be easier to understand because you now know exactly what an array does and what it costs.
Keep going
- 🎬 The 60-second visual version of this post: What Is an Array?
- 📚 Next in the series: Why do arrays start at index 0?
- đź”” New visual CS shorts every week on @onurthedev, with the deep dive landing here each time
- đź‘‹ More about me and what I build: ucaronur.com