TL;DR: AI has made us faster, but speed without reps quietly erodes the skills we need to supervise the code AI writes for us. Platforms like CodeCrafters give you a structured, test-driven way to rebuild real systems — Redis, Git, SQLite, a shell — with your own hands. Treat it as the gym your engineering craft now needs.
Generated AI image by Google Gemini Nano Banana
Introduction
Here’s a small, uncomfortable experiment. Open a blank file, switch off your AI assistant, and write a TCP server that speaks just enough of the Redis protocol to answer PING with PONG. No autocomplete. No chat window. Just you, the docs, and a terminal.
If that made you hesitate even slightly — you’re not alone, and it’s not a character flaw. It’s atrophy.
Back in April, I wrote Dont Let AI Dull Your Software Engineering Edge', which was about how you prompt — forming a hypothesis before asking, tracing generated code instead of accepting it. That post was about using AI well. This one is about the other half of the equation: deliberately spending time without it, so the muscles AI can’t exercise for you stay strong.
By the end of this post, you’ll understand:
- What the research now says about AI assistance and skill formation
- How CodeCrafters is designed to put the struggle back into learning
- What a first challenge actually feels like, with a hand-written example
- A practical routine for keeping your craft sharp alongside your AI-powered day job
The Atrophy Is Real (and Now Measured)
For a while, “AI is making us worse engineers” was mostly vibes. In January, that changed. Anthropic ran a randomised controlled trial with 52 developers learning a new Python library — half with AI assistance, half coding by hand. The AI group scored 17% lower on a follow-up comprehension quiz, and the speed-up they got wasn’t even statistically significant.
The detail that stuck with me: the widest gap was in debugging — spotting when code is wrong and explaining why. That’s precisely the skill you need most when your job increasingly becomes reviewing what an agent produced.
It wasn’t all doom, though. How people used AI mattered enormously. Participants who asked conceptual questions or used AI to check their understanding scored well; those who delegated code generation wholesale scored lowest. The researchers’ own summary is worth sitting with: “Cognitive effort—and even getting painfully stuck—is likely important for fostering mastery.”
A fair caveat: this study looked at junior engineers picking up an unfamiliar library with a chat-based assistant, not seasoned engineers working in a domain they know. But the authors themselves suspect agentic tools could make the effect stronger, not weaker. If you’ve spent the last year watching an agent loop through your codebase (I’ve written a whole series on building those loops), that should give you pause.
So the question becomes: where do we get our reps?
Enter CodeCrafters: Rebuild the Tools You Use Every Day
CodeCrafters is a Y Combinator–backed platform built on a simple premise: you learn how systems really work by rebuilding them from scratch. The team behind it also maintains the enormously popular build-your-own-x repository on GitHub, so this idea has deep roots.
The catalogue currently includes eleven challenges — your own Redis, Git, SQLite, Kafka, shell, grep, HTTP server, DNS server, BitTorrent client, interpreter, and (fittingly for this blog) Claude Code. These aren’t weekend toys. The Redis track alone runs to 97 stages, walking you from a bare TCP listener to replication and beyond.
How it works
The workflow is refreshingly unglamorous:
- Pick a challenge and a language. Python, Go, Rust, Java, TypeScript and many more are supported.
- Code in your own setup. Your IDE, your terminal, your debugger — no toy browser editor.
git pushto test. Each push runs the stage’s test suite against your implementation and reports back within seconds, with hints if you’re stuck.
That last point is what makes it work. You get a tight feedback loop — the same thing that makes AI feel so productive — but you are the one closing it.
A philosophy that matches the problem
What sold me wasn’t the catalogue; it was their philosophy page. It argues that tutorials feel like progress but mostly build recognition rather than ability, and that friction is the point. They’re also refreshingly honest about AI: they don’t tell you to abandon it, and they openly admit AI can solve many of their stages. Their counter is a line I’ll be stealing for conference talks: “You don’t go to the gym to move metal from A to B.”
That’s the key distinction. CodeCrafters doesn’t ban AI — the discipline is yours to bring. The platform gives you the weights; you decide not to use the forklift.
What a First Challenge Feels Like
To make this concrete, here’s roughly where the Redis track begins: a server that accepts connections and responds to commands in RESP (the REdis Serialization Protocol). Here’s a hand-written warm-up that goes a little past the first stages by handling multiple clients and ECHO (try writing your own version before reading it):
"""
mini_redis.py - a tiny Redis-compatible server, written by hand.
Supports PING and ECHO over the RESP protocol, with many clients at once.
Run: python3 mini_redis.py Test: redis-cli -p 6380 PING
"""
import asyncio
HOST, PORT = "127.0.0.1", 6380 # 6380 so we don't clash with a real Redis on 6379
class ProtocolError(Exception):
"""Raised when the client sends bytes that aren't valid RESP."""
async def read_command(reader: asyncio.StreamReader) -> list[str]:
"""
Parse one RESP array of bulk strings, e.g. b"*2\r\n$4\r\nECHO\r\n$2\r\nhi\r\n"
-> ["ECHO", "hi"]. readuntil/readexactly handle partial TCP reads for us.
"""
header = await reader.readuntil(b"\r\n") # e.g. b"*2\r\n"
if not header.startswith(b"*"):
raise ProtocolError(f"expected array, got {header!r}")
count = int(header[1:-2])
parts = []
for _ in range(count):
size_line = await reader.readuntil(b"\r\n") # e.g. b"$4\r\n"
if not size_line.startswith(b"$"):
raise ProtocolError(f"expected bulk string, got {size_line!r}")
size = int(size_line[1:-2])
data = await reader.readexactly(size + 2) # payload + trailing \r\n
parts.append(data[:-2].decode())
return parts
def bulk(value: str) -> bytes:
"""Encode a RESP bulk string: $<len>\r\n<data>\r\n (len is in bytes)."""
raw = value.encode()
return b"$" + str(len(raw)).encode() + b"\r\n" + raw + b"\r\n"
def handle(command: list[str]) -> bytes:
name, args = command[0].upper(), command[1:]
if name == "PING":
return b"+PONG\r\n" # simple string reply
if name == "ECHO" and len(args) == 1:
return bulk(args[0])
return f"-ERR unknown or malformed command '{name}'\r\n".encode()
async def serve_client(reader, writer):
peer = writer.get_extra_info("peername")
try:
while True: # one connection, many commands
command = await read_command(reader)
writer.write(handle(command))
await writer.drain()
except asyncio.IncompleteReadError:
pass # client hung up - normal
except (ProtocolError, ValueError) as err:
writer.write(f"-ERR protocol error: {err}\r\n".encode())
await writer.drain()
finally:
writer.close()
await writer.wait_closed()
print(f"closed {peer}")
async def main():
server = await asyncio.start_server(serve_client, HOST, PORT)
print(f"mini-redis listening on {HOST}:{PORT}")
async with server:
await server.serve_forever()
if __name__ == "__main__":
asyncio.run(main())
Seventy-odd lines. An AI would produce it in two seconds. But writing it yourself forces you through lessons no generated snippet teaches:
- TCP is a stream, not a message queue. A single
recv()can hand you half a command or three commands glued together. Usingreaduntil/readexactlyisn’t style — it’s correctness. - Lengths are in bytes, not characters. Send
ECHO héyand a naivelen(str)gives you a corrupted reply. Thebulk()helper encodes first for exactly this reason. - A connection outlives a command. Forget the
while Trueloop and your server answers onePINGand then goes silent — a bug that looks baffling until you’ve made it once.
That’s the kind of understanding that compounds. The next time a production Redis client times out, you won’t be guessing.
For my own first challenge, I skipped Redis and went straight for Build your own Shell — in Java. It felt like the natural next step after my Java comeback roadmap. I’d been rebuilding Java fundamentals on paper, and here was a chance to put them under real pressure: parsing commands, launching processes, and wiring up the plumbing a terminal hides from you every day. I did it on the free tier, and it’s been a genuinely positive experience (and more importantly - I had fun doing it 🙂). Every git push that turned a stage green felt earned in a way a generated diff never does. Doing it without AI also showed me exactly where my Java was solid, where it was still muscle memory and how to get better at both.
Common pitfalls when starting out
| Problem | Cause | Solution |
|---|---|---|
First PING works, second hangs |
Handling one command per connection | Loop reading commands until the client disconnects |
| Second client can’t connect | Blocking, single-threaded accept loop | Use asyncio, threads, or an event loop (a later stage will force this) |
| Garbled replies for non-ASCII input | Using character length instead of byte length | Encode to bytes before measuring |
nc / telnet gets a protocol error |
Real Redis also accepts “inline” commands; this sketch doesn’t | Test with redis-cli, or add inline parsing as a stretch goal |
| Tests fail but it “works locally” | Missing \r\n terminators |
Re-read the RESP spec — the tests are stricter than your eyes |
A Practical Routine: AI for Work, Reps for Craft
I’m not arguing for going back to 2019. I rebuilt my expense tracker in React Native with heavy AI help and I’d do it again. The goal is balance — use AI where delivery matters, and protect dedicated time where learning matters.
Do’s ✅
Schedule it like training. Two or three focused sessions a week beat a heroic weekend binge.
Switch the assistant off at the editor level, not just in your head. In VS Code, a workspace-level or similar
.vscode/settings.jsondoes the job:{ // Practice repo only: no inline AI suggestions, no ghost text "github.copilot.enable": { "*": false }, "editor.inlineSuggest.enabled": false }Read the primary source. Challenges nudge you towards specs — the RESP docs, Git’s object format, SQLite’s file format. Reading specs is itself a skill that’s atrophying.
Pick a language you want to deepen. Rebuilding a shell or Redis in a language you’re rusty in exercises two muscles at once — systems knowledge and language fluency.
Don’ts ❌
- Don’t paste the stage description into a chatbot. You’ll pass the tests and learn nothing — the Anthropic study’s lowest scorers were the pure delegators.
- Don’t peek at other solutions too early. Community solutions are brilliant after you pass a stage; before that, they rob you of the struggle.
- Don’t treat getting stuck as failure. Stuck is where the learning happens.
On cost
There’s a free tier with limited content, so you can try a challenge before committing. My Shell-in-Java run was entirely free, which says a lot about how much value you can get before paying anything. Paid memberships unlock everything (three-month, annual and lifetime options), and there are team plans for groups of five or more. CodeCrafters even has an expense page nudging you to ask your manager to fund it from the AI budget — which, for engineering leaders reading this, is a genuinely good use of it. Check the pricing page for current plans if you’re interested in supporting the platform. Otherwise, start with the free tier, master one or two challenges would be suffice. If you like to do more, the paid tiers are worth considering.
Be sure to check out the links below for further reading.
Further Reading
- CodeCrafters — Philosophy
- How AI assistance impacts the formation of coding skills — Anthropic Research
- build-your-own-x on GitHub
- Redis serialization protocol (RESP) specification
- Don’t Let AI Dull Your Software Engineering Edge
Conclusion
AI has given us the most productive era in software history. But productivity and competence are two different things, and the evidence now suggests the first can quietly eat into the second — especially in debugging and comprehension, the very skills we need to keep AI-generated systems honest.
CodeCrafters won’t fix that on its own. What it offers is a well-built gym: real systems, real specs, your own tools, and a feedback loop tight enough to keep you coming back. The discipline of leaving the AI switched off is still yours to bring - much akin to switching off your calculator when you’re doing calculations by hand. Only reach for the calculator when you’re stuck.
That’s the whole point — and it’s what this blog has always been about: mastering the craft, not just shipping the output.
Thus I highly recommend you trying out CodeCrafters for yourself. It’s a great way to keep your skills sharp alongside your AI-powered day job. I’m still keeping my craft sharp without losing my edge 🙂.
Your next steps:
- Pick one CodeCrafters challenge — Redis or Shell are great first choices — and complete the first three stages with AI fully disabled.
- Block out two recurring “no-AI” practice sessions in your calendar this week.
- Post your reflections on the experience and understand what you learned in getting unstuck as you go through the challenges.
- Only then - you discover growth and mastery in the process.
Till next time, Happy Coding! Keep Learning and Growing!