This is a learning and portfolio repository. It contains Python implementations of algorithms and data structures that I use to practice algorithmic thinking, testing, complexity analysis, and project organization.
- Implement common algorithms and data structures from first principles, rather than relying on built-in equivalents where the point is to practice the underlying technique.
- Write tests that cover edge cases (empty input, duplicates, cycles, unreachable nodes) and not just the typical case.
- Document the time and space complexity of each implementation based on what the code actually does.
- Keep the project structured the way a small, readable Python package should be structured.
algorithms-practice/
├── algorithms/
│ ├── searching/
│ │ └── binary_search.py
│ ├── sorting/
│ │ ├── quicksort.py
│ │ └── merge_sort.py
│ ├── graphs/
│ │ ├── traversal.py
│ │ └── shortest_path.py
│ ├── data_structures/
│ │ └── hash_table.py
│ └── dynamic_programming/
│ └── longest_common_subsequence.py
├── tests/
│ ├── searching/
│ ├── sorting/
│ ├── graphs/
│ ├── data_structures/
│ └── dynamic_programming/
├── .github/workflows/tests.yml
├── requirements.txt
└── README.md
Each implementation module lives under algorithms/<category>/ and has a
matching test file under tests/<category>/.
Requires Python 3.11+.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtpytestAll 58 tests pass as of the current version of this repository.
| Implementation | Category | Time complexity | Auxiliary space |
|---|---|---|---|
binary_search |
Searching | O(1) best, O(log n) average and worst | O(1) |
quicksort |
Sorting | O(n log n) average, O(n²) worst | O(n) average, O(n²) worst |
merge_sort |
Sorting | O(n log n) | O(n) |
breadth_first_search |
Graphs | O(V + E) | O(V) |
depth_first_search |
Graphs | O(V + E) | O(V) |
dijkstra |
Graphs | O((V + E) log V) | O(V + E) |
HashTable |
Data structures | O(1) average, O(n) worst | O(n) |
longest_common_subsequence |
Dynamic programming | O(n · m) | O(n · m) |
Complexity notes are also documented in each module's docstring, next to the implementation they describe.
- Sorting returns new lists. Both
quicksortandmerge_sortbuild and return new lists rather than sorting in place, which is why their auxiliary space is O(n) rather than O(log n) or O(1). - Hash table uses separate chaining. Each bucket holds a Python list
of
(key, value)pairs.hash()is used only to pick a bucket index; collisions are resolved by scanning the bucket, not by wrapping a dictionary. The table doubles its bucket count whenever the load factor (entries / buckets) would exceed 0.75. - Dijkstra's algorithm rejects negative edge weights. Once a vertex is
popped from the priority queue, this implementation assumes its
shortest distance is final. That assumption breaks with negative
weights, so
dijkstraraisesValueErrorif it finds one instead of silently returning an incorrect result. - Graph traversals require every vertex to be a key, including
vertices with no outgoing edges, so isolated and disconnected vertices
are handled the same way as connected ones. Both
breadth_first_searchanddepth_first_searchraiseKeyErrorif the given start vertex isn't in the graph.
- Algorithm analysis — working out best/average/worst-case time complexity and auxiliary space complexity by reasoning about what each implementation actually does, not by assuming a textbook bound applies.
- Testing and edge cases — writing tests for empty input, single elements, duplicates, cycles, disconnected components, and invalid input (negative weights, missing start vertices), not just the typical case.
- Data structures — implementing a hash table from scratch, including collision handling with separate chaining and resizing based on load factor.
- Graph algorithms — adjacency-list representations, traversal order, cycle handling, and shortest-path computation with a priority queue.
- Dynamic programming — building a DP table bottom-up and reconstructing an actual solution (the subsequence itself) by walking the table backward, rather than only computing a length.