Back FoxCode

It's just an array

Stacks, queues, heaps, trees - most reduce to arrays, numbers, and pointers. Here's what actually happens in memory.

·11 min read

C++ has a type called std::list. It's not an array. Python has a type called list. It is an array. Java has Stack, which extends Vector, which is an array. A binary heap is stored as a flat array. A float is an integer interpreted differently.

How much of what computer science calls "data structures" is actually a different thing - and how much is just an array with extra rules?

A float is just an integer

This sounds wrong, but it's literally true.

A 64-bit floating-point number (IEEE 754 double-precision) is stored as three groups of bits:

PartBitsWhat it stores
Sign1 bitPositive or negative
Exponent11 bitsThe power (scale)
Mantissa52 bitsThe actual digits

All three parts are integers. The hardware reads these 64 bits and interprets them as a floating-point number using a formula:

(1)sign×2exponent1023×(1+mantissa)(-1)^{sign} \times 2^{exponent - 1023} \times (1 + mantissa)

The bits themselves are indistinguishable from a regular 64-bit integer. The only difference is how the CPU reads them.

You can prove this in JavaScript:

const buffer = new ArrayBuffer(8)
const float = new Float64Array(buffer)
const int = new BigUint64Array(buffer)

float[0] = 3.14
console.log(int[0]) // 4614253070214989087n — same bits, read as integer

The same 64 bits. The same memory. One interpretation says 3.14, the other says 4614253070214989087. A float it's a different lens on the same bits.

This is also why 0.1 + 0.2 !== 0.3 in every language that uses IEEE 754. The mantissa has 52 bits of precision. The decimal 0.1 is a repeating fraction in binary (like 1/3 is in decimal) - it can't be stored exactly, so the hardware rounds it. The "float" abstraction hides this until it bites you.

A string is just an array of numbers

A string like "hello" is stored as:

'hello'.split('').map(c => c.charCodeAt(0))
// [104, 101, 108, 108, 111]

Five numbers. That's it. Each character is a Unicode code point - a number between 0 and 65,535 (for the Basic Multilingual Plane). The string "hello" and the array [104, 101, 108, 108, 111] are the same data. The difference is that String gives you .toUpperCase(), .slice(), .includes() - methods that interpret those numbers as text.

In C, this isn't even hidden. A string is literally char[] - an array of characters. And char is literally an 8-bit integer. 'A' is just the number 65. There's no separate "character type" that's fundamentally different from a number - it's a number the compiler agrees to print as a letter.

char c = 65;
printf("%c", c); // prints 'A'
printf("%d", c); // prints 65

Same bits. Different interpretation. Again.

A stack is just an array with rules

A stack is an array where you're only allowed to add and remove from the end. That's it. The entire concept of a "stack" is an array with a restriction:

// "Stack"
const stack = []
stack.push('a')  // add to end
stack.push('b')
stack.pop()      // remove from end → 'b'

// Array
const arr = []
arr.push('a')    // ...same thing
arr.push('b')
arr.pop()        // ...same thing → 'b'

There is no difference. None. Zero. In JavaScript, Python, Go, and most languages - a stack IS an array. Python's documentation literally has a section titled "Using Lists as Stacks." It doesn't even pretend these are different things.

Java created a dedicated Stack class. It extends Vector (which is just a synchronized array). The entire class is six methods that wrap array operations.

A queue is just an array with different rules

A queue is an array where you add to the end and remove from the front. In JavaScript:

const queue = []
queue.push('first')
queue.push('second')
queue.shift() // 'first'

shift() is theoretically O(n)O(n) because the engine has to reindex the remaining elements. In practice, V8 and SpiderMonkey have optimized this so aggressively that for arrays under ~50,000 elements it's effectively O(1)O(1). SpiderMonkey doesn't even move the data - it just increments an internal pointer.

A heap is an array that pretends to be a tree

A binary heap - the structure behind priority queues, used in Dijkstra's algorithm and task schedulers - is stored as a flat array. The parent of element at index ii is at (i1)/2\lfloor(i - 1) / 2\rfloor. The children are at 2i+12i + 1 and 2i+22i + 2.

// This array IS a valid min-heap:
const heap = [1, 3, 2, 7, 6, 4, 5]

//          1          ← index 0
//        /   \
//       3     2       ← indices 1, 2
//      / \   / \
//     7   6 4   5     ← indices 3, 4, 5, 6

No nodes. No pointers. No TreeNode class. Just an array with a formula for navigating it. The "tree" exists only in our imagination and in the index math. The memory layout is a contiguous array - identical to any other.

A float is integers with a formula. A heap is an array with a formula. The pattern keeps repeating.

What about linked lists?

This is where the pattern breaks. Linked lists are genuinely different.

An array stores elements next to each other in memory. A linked list stores each element in a separate node scattered across the heap, with each node holding a pointer to the next one:

// Array: [10, 20, 30] → contiguous in memory
// Memory: | 10 | 20 | 30 |

// Linked list: 10 → 20 → 30 → null
// Memory: | 10 | ptr | ... garbage ... | 20 | ptr | ... | 30 | null |

This isn't just a naming difference. The memory layout is fundamentally different, and it has real consequences:

ArrayLinked list
Access element by indexO(1)O(1) - direct offsetO(n)O(n) - walk from head
Insert at known positionO(n)O(n) - shift everything afterO(1)O(1) - repoint two pointers
Memory layoutContiguous, cache-friendlyScattered, cache-hostile
Memory overheadJust the dataData + pointer per node

This is also where naming gets confusing. In C++, std::list is specifically a doubly-linked list, and std::vector is a dynamic array. "Reverse a list" and "reverse a vector" sound like the same task, but the memory layout is completely different.

That said - Bjarne Stroustrup himself (the creator of C++) demonstrated that std::vector beats std::list in almost every benchmark, including insertion-heavy workloads where linked lists theoretically win. The reason: CPU cache. Contiguous memory lets the prefetcher do its job. Scattered nodes cause cache misses on every access. The theoretical O(1)O(1) insertion of linked lists loses to the practical cache efficiency of arrays by factors of 10x-80x.

Rust's standard library goes even further: "It is almost always better to use Vec or VecDeque instead of LinkedList." When the language that ships a linked list tells you not to use it.

Trees are objects pointing to objects

A tree is a node with a value and references to child nodes. In JavaScript:

const tree = {
  value: 1,
  left: {
    value: 2,
    left: { value: 4, left: null, right: null },
    right: { value: 5, left: null, right: null }
  },
  right: {
    value: 3,
    left: null,
    right: null
  }
}

It's nested objects. That's all a tree is - objects that reference other objects. The "tree" is a pattern of how you organize the references.

And references (pointers) are just numbers. A pointer is a memory address, which is a 32-bit or 64-bit integer. So a tree is objects containing numbers that point to other objects containing numbers. Which are stored as... bits in memory. Which are interpreted as... etc. etc.

The abstraction stack

"All problems in computer science can be solved by another level of indirection... except for the problem of too many levels of indirection."

David Wheeler, one of the first computer science PhDs in history

Here is every type discussed so far, stripped to what it actually is:

What we call itWhat it actually is
FloatInteger bits, interpreted with a formula
CharacterA number (Unicode code point)
StringArray of numbers (characters)
StackArray, restricted to push/pop
QueueArray, restricted to push/shift
HeapArray, navigated with index math
Linked listObjects (nodes) containing numbers (pointers)
TreeObjects containing references to other objects
Hash mapArray of buckets, indexed by a hash function

Everything reduces to three primitives: numbers, arrays of numbers, and pointers (which are numbers). The rest is interpretation. Rules. Agreements between the programmer and the compiler about how to read the same bits.

This is, in a very real sense, the entire field of computer science: inventing useful interpretations of numbers stored in arrays.

So why the separate names?

Naming creates a shared vocabulary. When someone says "use a stack," every developer knows: LIFO, push, pop. When someone says "use a priority queue", the interface is clear: elements come out sorted by priority, probably backed by a heap.

The names are communication shortcuts. They work the same way design patterns work - not because Observer is fundamentally different from "a list of callbacks you loop through and call," but because the name lets two developers skip 10 minutes of explanation.

The question is whether the naming needs to come with separate types, dedicated implementations, and entire textbook chapters - or whether the names alone are enough. Max Howell, the creator of Homebrew, was rejected by Google because he couldn't invert a binary tree on a whiteboard - while 90% of Google's engineers were using his software daily.

Half the languages skipped the ceremony

Look at what the most popular languages actually ship:

StructureJavaScriptPythonGo
Linked ListNoNocontainer/list (rarely used)
StackNoNoNo
QueueNocollections.dequeNo
TreeNoNoNo
HeapNoheapqInterface only

JavaScript doesn't have a Stack, a Queue, a LinkedList, or a Tree. Python calls its dynamic array a "list" (which, ironically, is NOT a linked list - it's a contiguous array). Go uses slices for everything.

Millions of developers build production software every day in these languages without dedicated stack or queue types. Because you don't need a type called Stack to use an array as a stack. You just need push() and pop().

When the names actually matter

There are cases where the distinction is genuinely important:

  • Database internals use B-trees and B+ trees for indexing - the tree structure enables O(logn)O(\log n) lookups across billions of rows. An array can't do that.
  • Your framework's router probably uses a trie (a specialized tree) for URL matching. Flat arrays would make routing O(n)O(n) per request.
  • Operating system schedulers use priority queues (heaps) for process scheduling. The heap property - O(1)O(1) access to the highest priority element - is genuinely useful here.
  • Dijkstra's algorithm uses a priority queue to always process the closest unvisited node first. You could use an unsorted array and scan for the minimum each time, but that makes the algorithm O(V2)O(V^2) instead of O((V+E)logV)O((V + E) \log V). The named structure carries a real performance guarantee.

These are all infrastructure-level concerns. Databases, routers, schedulers, graph algorithms. If you're building a web app, a REST API, a dashboard - you interact with the results of these structures, not the structures themselves. The DOM is a tree, but you use querySelector(). The router uses a trie, but you write app.get('/users/:id', handler).


Everything is just arrays, numbers, and interpretation. The names are useful when they communicate intent. They're less useful when they become the point.

Maybe it's because I'm a web developer, or maybe it's because I'm a JavaScript developer and in my world, almost everything is an object, a Map is an object with better manners, an array is a stack, queue, list, and typeof null === 'object' is not a bug but a feature.

Continue Reading