Rung 2 — Data Structure Library from Scratch¶
Month: M2–M3 | Mode: Implementation | Platform: GitHub (public)
There is a meaningful difference between a developer who can use a HashMap and a developer who can build one. The first can call map.get(key). The second understands why that call is O(1) amortized, what happens when the load factor exceeds 0.75, and what separate chaining looks like at the memory layout level. This rung is about becoming the second kind of developer.
Every data structure you implement here will later be used — sometimes literally, always conceptually — in LeetCode hard problems and Codeforces problems. When you understand the mechanism, the problem becomes a question about mechanism. When you don’t, it remains a question about memorized syntax.
What It Is¶
A GitHub repository named ds-from-scratch containing clean, tested implementations of 7 fundamental data structures. Each implementation is written by you, from your understanding, in a language you’re comfortable with (Java, C, or C++ — all work).
The constraint that matters: no copy-paste from Stack Overflow, no AI generation. You can use a reference (CLRS, a textbook, your own notes) to understand the algorithm, but the code must be yours. The tests must be yours. The README for each structure must be yours.
The 7 Data Structures¶
1. Dynamic Array¶
A resizable array that doubles in capacity when full.
Must implement:
push(element)— append to end, resize if neededget(index)— O(1) accessset(index, element)— O(1) updatedelete(index)— shift elements, O(n)size()andcapacity()— both exposedInternal: private
resize()that doubles capacity and copies
Key design decision to document: Why doubling (not +1, not +10)? What growth factor produces O(1) amortized push?
Test cases (minimum 10):
Push elements until a resize is triggered; verify capacity doubled
Get on empty array throws exception or returns sentinel
Delete at index 0, middle, last
Push 1000 elements; verify size is 1000 and all are retrievable
Edge: delete the only element
2. Doubly Linked List¶
A linked list with both next and prev pointers per node.
Must implement:
insertFront(element)insertBack(element)insertAfter(node, element)delete(node)— O(1) given the nodefind(element)— O(n) search returning node or nulltoArray()— for test assertions
Key design decision to document: Why O(1) delete given the node? What does having both pointers enable that a singly linked list cannot do?
Test cases (minimum 10):
Insert front + insert back: verify ordering
Delete head, tail, middle node
Insert after a specific node
Find element that doesn’t exist
Delete the only node (list becomes empty)
3. Stack (Array-backed)¶
A LIFO stack backed by a dynamic array (use your own Dynamic Array if done, or the language’s built-in array with manual size tracking).
Must implement:
push(element)pop()— returns top element, raises exception if emptypeek()— returns top without removingisEmpty()size()
Key design decision to document: Array-backed stack vs. linked-list-backed stack — trade-offs in memory allocation pattern and cache performance.
Test cases (minimum 10):
Push N elements, pop all N in reverse order
Pop on empty stack: exception or error
Peek doesn’t modify the stack
Interleaved push/pop
Stack with a single element: push, peek, pop, confirm empty
4. Queue (Circular Buffer)¶
A FIFO queue backed by a fixed-size circular buffer (array with front and rear pointers modulo capacity).
Must implement:
enqueue(element)— add to reardequeue()— remove from frontpeek()— return front without removingisFull(),isEmpty(),size()Internal: pointer arithmetic using
% capacity
Key design decision to document: Why circular buffer instead of a simple array with shifting? What makes both enqueue and dequeue O(1)?
Test cases (minimum 10):
Enqueue until full, verify isFull
Dequeue from empty: exception or error
Enqueue 5, dequeue 3, enqueue 3 more — verify wrap-around works
Size stays correct across interleaved enqueue/dequeue
Enqueue 1 element, dequeue it, enqueue again (reuse slot)
5. Min-Heap¶
A binary min-heap backed by an array, where parent(i) = (i-1)/2, left(i) = 2i+1, right(i) = 2i+2.
Must implement:
insert(element)— push to end, heapify-upextractMin()— swap root with last, pop, heapify-downpeekMin()— return root without removingsize()Internal:
heapifyUp(index)andheapifyDown(index)
Key design decision to document: Why array indexing works for a binary tree. Why heapify-up is O(log n) — derive via height of tree.
Test cases (minimum 10):
Insert [5, 3, 8, 1, 4]: extractMin should return 1, then 3, then 4…
Insert duplicate values
Extract from a 1-element heap
Extract from empty heap: exception
Insert 100 random integers, extract all: verify sorted ascending
6. HashMap (Separate Chaining)¶
A hash map using an array of linked lists (buckets) with a load factor threshold that triggers rehashing.
Must implement:
put(key, value)— insert or updateget(key)— returns value or null/exceptiondelete(key)containsKey(key)size()Internal:
hash(key)function,rehash()when load factor > 0.75
Key design decision to document: Why load factor 0.75 is a common threshold. What happens to average chain length as load factor increases. Why rehashing doubles the bucket count.
Test cases (minimum 10):
Put 10 keys, get all 10
Update an existing key: get returns new value
Delete a key, confirm containsKey returns false
Get on missing key: null or exception
Put enough keys to trigger rehash: all keys still retrievable after rehash
Hash collision: two keys that map to same bucket, both retrievable
7. Trie¶
A prefix tree for string keys.
Must implement:
insert(word)— add word to triesearch(word)— return true if exact word existsstartsWith(prefix)— return true if any word has this prefixdelete(word)— remove word (mark end-of-word flag, prune dead branches optional)
Key design decision to document: Why Trie search is O(L) where L is word length (not O(n) where n is number of words). When a Trie outperforms a HashMap for string keys.
Test cases (minimum 10):
Insert [“apple”, “app”, “application”]: search(“app”) = true, search(“ap”) = false
startsWith(“app”) = true, startsWith(“ban”) = false
Delete “app”: search(“app”) = false, search(“apple”) = true (subtree preserved)
Insert and search empty string (edge case)
Insert same word twice: no duplication, search still returns true
Repository Structure¶
ds-from-scratch/
├── README.md ← Overview + links to each DS
├── dynamic-array/
│ ├── DynamicArray.java ← (or .c / .cpp)
│ ├── DynamicArrayTest.java
│ └── README.md
├── doubly-linked-list/
│ ├── DoublyLinkedList.java
│ ├── DoublyLinkedListTest.java
│ └── README.md
├── stack/
│ ├── Stack.java
│ ├── StackTest.java
│ └── README.md
├── queue/
│ ├── CircularQueue.java
│ ├── CircularQueueTest.java
│ └── README.md
├── min-heap/
│ ├── MinHeap.java
│ ├── MinHeapTest.java
│ └── README.md
├── hashmap/
│ ├── HashMap.java
│ ├── HashMapTest.java
│ └── README.md
└── trie/
├── Trie.java
├── TrieTest.java
└── README.md
Acceptance Criteria¶
All 7 data structures implemented
All test files present with minimum 10 tests each
All tests pass (run them; don’t just write them)
Each data structure has its own
README.mdwith: what it is, key design decisions, complexity tableA peer can read the code and understand what it does without you explaining it
No AI-generated code (the point is that you built the understanding, not the file)
Repo is public and linked from your GitHub profile
Where to Publish¶
Create a public GitHub repository named
ds-from-scratchTop-level
README.mdlinks to each data structure’s subdirectoryInclude a complexity table at the top: operation | DS | time | space
Add to portfolio README and optionally pin to GitHub profile alongside
complexity-audit
Signal It Sends¶
“This person can implement abstractions from scratch, not just use library functions.”
The bar for this signal is surprisingly low to meet but very few people actually do it. Every working engineer uses data structures daily. Maybe 10% have ever implemented a hash map. Maybe 2% have implemented one with proper rehashing and tests. A public repo with 7 clean implementations and 70+ passing tests is a concrete artifact that takes “I know data structures” from a claim to a fact.
It also separates you from engineers who learned DS for interviews and forgot them afterward. This repo becomes a reference you’ll actually use.
Practical Notes¶
Start with Dynamic Array. It’s the simplest and establishes the pattern (implementation file + test file + README) you’ll replicate six more times.
The Min-Heap is the most transferable. Heap operations appear constantly in graph algorithms (Dijkstra), scheduling, and stream processing. Get it right.
The HashMap is the most conceptually rich. Budget extra time. The rehashing logic is where most first-time implementations have bugs.
For testing: use your language’s built-in test framework (JUnit for Java, assert for C/C++ with a test runner). If you don’t know a test framework, write a
main()that prints PASS/FAIL and verifies manually — that’s fine for this purpose.If you get stuck on a specific DS, look at pseudocode in CLRS or a trusted reference to understand the algorithm — then close the reference and write the code yourself.
Timeline¶
Week |
Target |
|---|---|
Week 1 (overlap M2) |
Dynamic Array + Stack (simpler structures) |
Week 2 (M2/M3) |
Doubly Linked List + Circular Queue |
Week 3 (M3) |
Min-Heap |
Week 4 (M3) |
HashMap with rehashing |
Week 5 (M3) |
Trie |
Week 6 (M3) |
Review, add missing tests, clean READMEs, publish |