LearnThatStack Ace your next interview

Arrays, Strings & Hash Tables

Code in Python unless a row names the language Checked Oct 2026 35 code snippets

The model

  • An array is one contiguous block of same-size slots. base + i * size finds any slot in one step, so indexing is O(1). An insert in the middle shifts every later element, so it's O(n).
  • When a dynamic array fills up, it copies everything into a block a constant factor bigger, often 1.5x or 2x. That append is O(n), but copies get rarer as it grows, so appends average O(1). That is amortized O(1).
  • A string is an array of characters. Java, Python and JavaScript make it immutable, so every edit builds a new string. That's why += in a loop is O(n^2).
  • A hash table is an array plus a hash function that turns each key into a slot index. Lookups average O(1). Collisions and a crowded table push them toward O(n).
  • The hash only picks the bucket, and equality confirms the key. So equal keys must hash the same, and a stored key must never change.
Keyany hashable valueHash functionsame key, same hashBucket arrayone contiguous blockEntrystored key + valueFINDhash(key)hash % n slot indexbase + i * size O(1)same key? via equalsCOLLIDEno match try the nextGROWrehash all load > 0.75
A lookup hashes the key, picks a slot, jumps there in O(1) and checks the key. A collision tries the next entry. Growing rehashes every key in O(n).

Arrays in memory

why reads by index are fast and inserts are slow

How an array sits in memory

Say how an array is storedOne contiguous block of same-size slots, with no gaps or pointers.
Find element i's addressbase + i * sizeSame cost at any index.
Weigh an array's strengths and costsFast reads by index and in order. Slow inserts in the middle.

TipThe block is one piece. You usually can't grow it in place, because the memory after it may be taken.

Array operation costs

Add to the enda.append(x)Amortized O(1). Nothing has to shift.
Insert or delete in the middlea.insert(k, x)del a[k]O(n). Everything after k shifts.
Check whether an array holds a valuex in aO(n). It checks slot by slot.
Delete when order doesn't mattera[k] = a[-1]a.pop()O(1). The last element fills the gap.

TipIf you check membership often, copy the values into a set once. Each check then averages O(1).

TrapSwap-remove moves the last element to a new index. Anything holding its old index is now wrong.

Zero-based indexes and bounds

Explain why indexes start at zeroAn index is an offset from the start. In C, a[i] means *(a + i).
Index past the end in Java or PythonJava throws ArrayIndexOutOfBoundsException. Python raises IndexError.
Read or write past the end in JavaScriptA read returns undefined. A write grows the array.
Index past the end in C or C++Undefined behavior. It may read garbage, crash or seem to work.

TrapIn C, a write past the end can overwrite nearby data. Attackers use these buffer overflows to run their own code.

Arrays and the CPU cache

Explain why arrays scan fastEach cache line holds several elements, and the prefetcher reads ahead.
Choose an array or a linked listAn array, unless you splice often at a node you already hold.
Loop over a 2D gridRows outer, columns inner. Most languages store it row by row.

TrapLooping column by column jumps a whole row each step. On a large grid, nearly every read misses the cache.

Dynamic arrays

how a list grows, and what one append costs

Grow a dynamic array

Tell a static array from a dynamic oneA static array's size is fixed. A dynamic one, like list, resizes.
Tell length from capacityLength counts stored elements. Capacity counts allocated slots.
Append to a full arrayCopy everything to a bigger block. That one append is O(n).
Explain amortized O(1) appendDoubling makes resizes rare. All the copying adds up to under 2n.

TipWhen you know the count, presize with new ArrayList<>(n) in Java or v.reserve(n) in C++.

Grow and shrink the capacity

Grow by 2xFewer copies, but up to half the memory can sit unused.
Grow by 1.5xLess waste, but more copies. Java's ArrayList grows by half.
Grow by a fixed amount, like 10 slotsResizes stay frequent, so n appends copy O(n^2) in total.
Decide when to shrinkHalve the capacity once it's a quarter full, not half.
Trim spare capacity in Javalist.trimToSize()ArrayList never shrinks on its own.

TipWith 2x, a new block is bigger than all the freed ones combined, so the allocator can't reuse them. With 1.5x it can.

TrapIf you shrink at half full, adding and removing one element at that boundary resizes every time.

Arrays near a billion elements

Estimate a resize's memory peakOld and new buffers coexist. A billion 8-byte items peak near 24 GB.
Spot the index overflowA 32-bit index overflows past about two billion elements.
Allocate one huge blockFree memory may be split into gaps that are all too small.
Scan an array of objectsSlots point all over the heap, so reads miss the cache.

TipPast this scale, use chunked structures, not one flat array. For hot scans, store plain values.

Strings

why edits copy, and what a length counts

Immutable strings

Change or append to a strings[:i] + c + s[i+1:]It builds a new string, O(n).
Say why strings are immutableLock-free sharing between threads, and a hash you compute once.
Copy a large string or array cheaplyCopy-on-write. Copies share a buffer until the first write.

TipJava, Python, JavaScript, C# and Go strings are immutable. C++ std::string and Rust's String are mutable.

TrapUnder copy-on-write, a loop of small edits can copy the whole buffer on nearly every step.

Build a string in a loop

Append with += in a loops += cO(n^2). Each += copies the string so far.
Build a string in Python"".join(parts)O(n). Join once at the end.
Build a string in Javasb.append(c)sb.toString()O(n) in total.
Edit the middle of a huge textA rope, a tree of chunks. O(log n) edits, slower scans.

TipFor two or three fixed pieces, plain + is fine.

TrapV8 hides the += cost by linking pieces into a rope. Other runtimes copy, so don't count on it.

Length, bytes and encoding

Count a string's bytes in UTF-8len(s.encode("utf-8"))1 to 4 per code point. "café" is 5.
Read s.length in JavaScriptIt counts UTF-16 code units. Many emoji count as 2.
Get a C string's lengthstrlen(s)O(n). It scans for the zero byte. A stored length is O(1).

TrapCutting by bytes or code units can split a character and corrupt the text.

Substrings, interning and short strings

Take a substring without copyingA view is O(1), but it keeps the whole parent alive.
Intern a stringsys.intern(s)Equal text shares one object, so equality is a pointer check.
Store a short string without a heap allocationSmall-string optimization, up to 15 or 22 bytes inline.

TipSmall-string optimization costs a bigger object and a branch on every access.

TrapInterning mostly unique strings only grows the pool. In .NET, interned strings usually live until the process exits.

Hash maps & sets

store and find by key in O(1) on average

Hash map basics

Insert, update or delete a keyd[k] = vdel d[k]
Look up a keyd.get(k)k in dd[k] raises KeyError if missing.
Explain average O(1) lookupThe hash leads to one bucket, which holds about one entry.
Name a hash map's costsNo order, memory on empty slots, and a slow insert on resize.

TipUse a hash map first for "seen it before?" checks and "how many of each?" counts.

Solve problems with sets and maps

Choose a set or a mapA set holds keys only. A map also holds a value for each key.
Remove duplicates and keep the orderlist(dict.fromkeys(xs))O(n). set(xs) loses the order.
Find two numbers that add up to a targetOne pass. Look up target-x among values seen so far.
Check whether two words are anagramsCounter(a) == Counter(b)O(n). Sorting is O(n log n).

TrapBroken equality lets duplicates into a set, and crafted keys can make one pass O(n^2).

Iteration order

Loop over a plain hash mapExpect no order. Python's dict and JavaScript's Map keep insertion order.
Keep insertion order in Javanew LinkedHashMap<>()It links the entries in order, at two extra pointers each.
Build an LRU cache in Javanew LinkedHashMap<>(16, 0.75f, true)true turns on access order. Evict in removeEldestEntry.
Change a Java map while you loop over itThe next step throws ConcurrentModificationException.

TrapCode that relies on hash map order has a silent bug. A resize or a random seed can reorder it.

Hash functions

how a key becomes a bucket number

From key to bucket

Turn a key into a bucket indexhash(key) % nHash it, then take the remainder by the bucket count.
Explain why collisions happenFar more keys than buckets, so some must share. That's the pigeonhole principle.
Tell a hash function from a hash tableThe function makes a number. The table stores the entries.

TrapThe same key must always give the same hash, or you can't find what you stored.

Write a simple hash function

def sum_hash(key, size):ignores order return sum(ord(c) for c in key) % sizedef poly_hash(key, size):order counts h = 0 for c in key: h = 31 * h + ord(c)as in Java's String.hashCode return h % size
Spot the flaw in summing character codesIt ignores letter order, so "cat" and "act" always collide.

Good hash, bad hash

Name what makes a hash function goodAn even spread, cheap to compute, and the same output for the same key.
Size the table to a power of twohash(key) & (n - 1)A fast mask, but it keeps only the low bits. Mix the hash first.
Size the table to a primehash(key) % pIt hides a weak hash, but division is slower.

TrapReal keys aren't random. Sequential IDs or similar strings can push a naive hash toward O(n).

What hashing is for

Say what hashing isData of any size in, a short fixed-size value out. Same input, same hash.
Tell hashing from encryptionHashing has no key to decrypt with, and SHA-256 can't be reversed.
Name where hashing is usedMaps, caches, duplicate checks, checksums, Git, passwords.
Estimate when 64-bit hashes collideAfter about 2^32, or four billion keys. That's the birthday bound.

TrapLookup hashes are easy to collide on purpose. Use a keyed or cryptographic hash for attacker input.

Collisions

what a table does when two keys share a bucket

Chaining or open addressing

012catact
chainingact lands in bucket 1 with cat and joins that bucket's list.
hash(act)catdogact
probingact hashes to cat's slot, steps past dog, and lands in the next free slot.
Resolve collisions by chainingEach bucket holds a list. A lookup walks it, comparing keys.
Resolve collisions by open addressingEvery entry lives in the array. A taken slot sends the key on.
Store more entries than bucketsOnly chaining can. Open addressing needs a load factor below 1.
Delete from an open-addressed tableLeave a tombstone, or later lookups stop too early.

TipJava's HashMap chains. Python's dict uses open addressing.

TrapTombstones count as full while probing. They pile up until a rehash clears them.

Probe sequences

Probe linearly(h(k) + i) % mThe cache-friendly default.
Probe quadratically(h(k) + i * i) % mKeys with the same home slot share a path.
Probe with double hashing(h(k) + i * h2(k)) % mThe best spread. h2 must never be 0.
Explain primary clusteringFilled slots merge into runs. A key that lands in a run walks to its end.

TrapQuadratic probing can skip empty slots. Use a prime table size and keep it under half full.

How each scheme uses memory and cache

Compare chaining and open addressing on memoryChaining pays a pointer per entry. Open addressing leaves 30 to 50% of slots empty.
Explain why open addressing is often fasterProbes stay in one array, while chain hops can miss the cache.
Estimate probes at a high load factorAbout 1/(1-a) at load a. 2 probes at 0.5, 10 at 0.9, 100 at 0.99.

TipBelow about 0.7 load, open addressing usually wins on lookups.

Robin Hood, cuckoo and perfect hashing

Even out probe lengthsRobin Hood hashing. A key that has probed farther takes the slot.
Guarantee O(1) lookups in the worst caseCuckoo hashing. Each key has two slots, and a lookup checks only those.
Look up a fixed key set with no collisionsA minimal perfect hash. n keys fill n slots, but any change means a rebuild.

TrapA cuckoo insert can evict keys in a loop and force a rebuild. Two tables stall near 50% full.

Keys & equality

the hash picks a bucket, and equality finds the key

hashCode and equals

Tell apart two keys in one bucketThe map compares them with equals. The hash only picks the bucket.
State the hashCode and equals contractEqual objects need equal hash codes. The reverse isn't required.
Override equals in Javareturn Objects.hash(name, id);Also override hashCode from the same fields.
Change a key after storing itIt now hashes elsewhere, so lookups miss the entry. Nothing throws.

TrapOverride equals without hashCode, and equal objects get different default hashes. A lookup for one misses the other.

Write a hash map's put

def put(self, key, val): b = self.table[hash(key) % len(self.table)] for pair in b: if pair[0] == key:equality confirms the key pair[1] = val; return b.append([key, val]); self.size += 1 if self.size > 0.75 * len(self.table): self.grow()double, then rehash all
Write get and deleteWalk the same bucket, comparing keys. Delete unlinks the match.
List the design decisionsCollision scheme, resize threshold, bucket count and a seeded hash.

Load factor & resizing

when a table grows, and what growing costs

Load factor

Work out the load factorentries / buckets6 entries in 8 buckets is 0.75.
Say when Java's HashMap growsPast 0.75 load. It doubles and rehashes every entry.
Say when Python's dict growsAt two thirds full. It roughly doubles too.
Set the limit for open addressingLower, often 0.5 to 0.7. Probe runs get long fast.

TipAt 0.75, most buckets hold zero or one entry. Java's docs call it a good tradeoff between time and space.

Resize a hash table

Pay for one resizeO(n) for that insert, but doubling keeps it rare. Average stays O(1).
Resize without a long pauseKeep both tables and move a few buckets per operation, like Redis.
Grow without rehashing every keyExtendible or linear hashing. Each step splits just one bucket.

TrapAmortized means on average. The one insert that triggers a resize still stalls, and latency-sensitive systems feel it.

Attacks & concurrency

keep O(1) under hostile keys and many threads

When O(1) becomes O(n)

Name what makes lookups O(n)A poor hash, a table left too full, or an attacker's colliding keys.
Spot a hash-flooding attackCrafted keys share one bucket, so n inserts cost O(n^2).
Defend with a keyed hashUse SipHash with a secret per-process seed.

TipJava 8+ turns a long chain into a tree, so a flooded bucket of comparable keys costs O(log n).

TrapA leaked seed lets attackers precompute collisions again. Keep it secret and unique to each process.

Share arrays and maps across threads

Make a hash map fast under many threadsnew ConcurrentHashMap<>()Per-bin locks and lock-free reads.
Make a dynamic array thread-safeLock it, or swap buffers atomically and free old ones after readers leave.
Spot false sharingThreads write neighbors on one cache line, which bounces between cores.
Fix false sharingPad each thread's hot data onto its own cache line.

TrapOne global lock is safe, but every reader waits in line behind it.

Hashing at scale

keys across servers, and sets too big to store

Spread keys over servers

Move few keys when a node joins or leavesConsistent hashing. A node change moves about 1/N of keys, not nearly all.
Even out load on the ringGive each server many ring positions, called virtual nodes.
Move a key range to a new nodeStream that range while the old and new owners both serve reads.

TipA key goes to the first server clockwise from it on the ring.

TrapConsistent hashing doesn't fix a hot key. One popular key still overloads one shard.

Bloom filters and HyperLogLog

Test membership in a fraction of the memoryA Bloom filter. No means absent and yes means maybe, at a few bits per key.
Count distinct items without storing themHyperLogLog. A few kilobytes estimate a billion items within about 2%.
Count distinct items across shardsMerge the per-shard sketches. Nothing is counted twice.

TrapPast its planned size, a Bloom filter's false-positive rate climbs. A basic one can't delete keys.

Found this useful? Pass it on.
Pro · $10/mo

This sheet stays free. With Pro, you can:

  • Every answer on this sheet. Read the full answer to all 85 questions it links, not just the 11 free ones.
  • My sheet. The cards you star and the ones you miss in Quiz me, from every sheet, on one page to quiz and print.
  • PDFs. Download this sheet and My sheet, laid out for A4, with every question a link to its answer.
  • The rest of Pro. Every answer in the question library, and 1,000 AI credits a month.