Why a foundations page about memory
Because “just use a hash map” is advice, not understanding. A hash map buys you O(1) lookup and costs you O(n) space plus a hash computation per access. Once you can price that trade, you stop guessing at solutions and start choosing them.
The structures, physically
Array / vector<int> / JS array — a contiguous block. Index access is one pointer
arithmetic operation, hence O(1). Inserting in the middle shifts everything after it, hence
O(n). Appending is amortised O(1): most appends are free, and the occasional doubling-resize is
paid off across all the cheap ones.
Difference: a C++ vector<int> really is a contiguous block of ints. A JS array is an object
that the engine usually optimises into a contiguous block — until you make it sparse or mix
types, at which point it silently degrades to a dictionary. Keep JS arrays dense and
single-typed in hot loops.
Hash map — an array of buckets plus a hash function. Average O(1), worst case O(n) if everything collides. The cost you’re paying is memory plus hashing per access.
Difference: C++ unordered_map hashes; map is a balanced tree with O(log n) ops but keeps
keys sorted. JS Map preserves insertion order and takes any key type; a plain {} coerces all
keys to strings, which is a real bug source when your keys are numbers.
Linked list — nodes scattered in memory, each pointing to the next. O(1) insert/delete at a known node, but O(n) to find that node, and terrible cache behaviour. That’s why arrays beat linked lists in practice far more often than the complexity table suggests.
String — in C++ std::string is mutable and s += c is amortised O(1). In JavaScript
strings are immutable, so s += c in a loop can be O(n²). Push characters into an array and
join('') at the end.
The translation table
| Need | C++ | JavaScript |
|---|---|---|
| Dynamic array | vector<int> v; v.push_back(x); | const v = []; v.push(x); |
| 2-D array | vector<vector<int>> g(R, vector<int>(C, 0)); | Array.from({length:R},()=>Array(C).fill(0)) |
| Hash map | unordered_map<int,int> m; m[k]++; | const m = new Map(); m.set(k,(m.get(k)??0)+1); |
| Hash set | unordered_set<int> s; s.insert(x); | const s = new Set(); s.add(x); |
| Sort | sort(v.begin(), v.end()); | v.sort((a,b)=>a-b); |
| Sort by key | sort(v.begin(),v.end(),[](auto&a,auto&b){return a[0]<b[0];}); | v.sort((a,b)=>a[0]-b[0]); |
| Min-heap | priority_queue<int, vector<int>, greater<int>> pq; | none built in — write a MinHeap once |
| Stack | stack<int> st; | array + push/pop |
| Queue | queue<int> q; | array + an index pointer (not shift()) |
| Deque | deque<int> dq; | array with push/pop/shift/unshift |
| Build a string | string s; s += c; | const parts=[]; parts.push(c); parts.join('') |
| Char to index | c - 'a' | c.charCodeAt(0) - 97 |
| Integer limits | INT_MAX, LLONG_MAX | Number.MAX_SAFE_INTEGER (2⁵³−1) |
| Integer division | a / b for ints | Math.trunc(a/b) |
JavaScript gotchas that cost interviews
[10, 9, 1].sort()gives[1, 10, 9]. The default sort is lexicographic. Always pass a comparator.arr.shift()is O(n). A BFS queue built onshift()is O(n²). Use an index pointer:let head = 0; while (head < q.length) { const u = q[head++]; ... }- Strings are immutable — build with an array and
join(''). - No integer type. Bitwise operators coerce to 32-bit signed, so
1 << 31goes negative and large XOR results surprise you. {}stringifies keys.obj[1]andobj['1']are the same slot. UseMapwhen keys are numbers or objects.- No built-in heap. Write a
MinHeapclass once, memorise it, reuse it all quarter.
C++ gotchas that cost interviews
m[k]on a map inserts. Testing membership withif (m[k])silently creates the entry. Usem.count(k)orm.find(k) != m.end().- Signed overflow is undefined behaviour. Use
long longwhen summing or multiplying anything that could exceed ~2·10⁹. (l + r) / 2can overflow. Writel + (r - l) / 2. Always.priority_queueis a max-heap by default. A min-heap needspriority_queue<int, vector<int>, greater<int>>.- Passing containers by value copies them. Use
const vector<int>&for parameters andvector<int>&when you mean to modify.
The rule for this quarter
Solve in one language, immediately port to the other, before writing anything up. If the port takes three minutes, you understood the algorithm. If it takes twenty, you memorised syntax — which means you should redo the problem, not the translation.