Skip to content
My CS Companion

Build a database. Learn computer science.

Write real code. Watch your system improve across 5 milestones. Guided by AI, benchmarked against real implementations.

func (kv *KVStore) Put(key string, value []byte) error {
    kv.mu.Lock()
    defer kv.mu.Unlock()

    kv.store[key] = value
    return kv.flushToDisk(key, value)
}
Example KVStore.Put function from Milestone 1

-> 25,412 inserts/sec

Five milestones. One database. Seven CS subjects.

Milestone 1: Key-Value Store

Implement Get, Put, Delete with disk persistence. Handle concurrent access.

CS concepts: Hash maps, file I/O, serialization

80% scaffolded

Milestone 2: Storage Engine

Add a write-ahead log for crash recovery. Implement compaction.

CS concepts: Write-ahead logging, crash recovery, log-structured storage

~60% scaffolded

Milestone 3: B-Tree Indexing

Build a B-tree index. Implement node splits and range scans.

CS concepts: Tree data structures, disk-based indexing, range queries

40% scaffolded

Milestone 4: Query Parser

Parse and execute SQL queries against your storage engine.

CS concepts: Lexing, parsing, query planning, abstract syntax trees

~20% scaffolded

Milestone 5: ACID Transactions

Add transactional semantics with rollback and durability guarantees.

CS concepts: Concurrency control, isolation levels, write-ahead log recovery

~15% scaffolded

Milestone 1: Key-Value Store

A key-value store that persists data to disk. You'll implement Get, Put, Delete, and a serialization layer that writes your data to a file and reads it back on startup.

What You'll Learn

  • Byte-level I/O in Go — reading and writing raw bytes, not just strings
  • Binary serialization — designing a format that encodes length-prefixed data so you can parse it back reliably
  • File operations — creating, writing, reading, and closing files using Go's os package
  • The gap between memory and disk — why in-memory data structures need a persistence strategy

Starter Code

type KVStore struct {
	mu       sync.Mutex
	data     map[string]string
	filePath string
	file     *os.File
}

// ...

// Get retrieves the value associated with the given key.
// Returns the value and true if found, or an empty string and false if not.
//
// TODO: Implement this method.
// - Look up the key in s.data
// - Return the value and whether it was found
func (s *KVStore) Get(key string) (string, bool) {
	s.mu.Lock()
	defer s.mu.Unlock()

	// TODO: implement Get
	return "", false
}

// Put stores a key-value pair and persists the change to disk.
//
// TODO: Implement this method.
// - Store the key-value pair in s.data
// - Call s.saveToDisk() to persist
// - Return any error from saving
func (s *KVStore) Put(key, value string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	// TODO: implement Put
	return nil
}

// Delete removes a key from the store and persists the change to disk.
//
// TODO: Implement this method.
// - Remove the key from s.data
// - Call s.saveToDisk() to persist
// - Return any error from saving
func (s *KVStore) Delete(key string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	// TODO: implement Delete
	return nil
}

80% scaffolded. Your job: implement the TODO functions.

Acceptance Criteria

  1. put-and-get — Put a key-value pair and retrieve it with Get.
  2. get-missing-key — Get on a nonexistent key returns not-found.
  3. delete-key — Delete removes a key so subsequent Get returns not-found.
  4. overwrite-key — Put with an existing key updates the value.
  5. persistence-write — Data file exists on disk after Put and Close.
  6. persistence-reload — A new KVStore instance loads previously persisted data.
  7. multiple-keys — Store handles 100+ key-value pairs correctly.
  8. exit-clean — Program exits with code 0 on success.

Results: 1,000 inserts, avg 39.12ms, 25,412 ops/sec