← All notes

Field note

August 25, 2026

Going back to C, seriously

I’m returning to C to get closer to the fundamentals of backend development—memory, sockets, processes, files, and the work frameworks usually hide.

CBackendSystemsLearning

I have used C before, but there is a difference between knowing enough C to finish a project and studying it seriously enough to let it change how you understand software. I want to do the second one now.

The goal is not to replace the tools I use for everyday backend work. PHP, frameworks, databases, containers, and managed services exist for good reasons. They let us solve product problems without rebuilding the operating system on every project. I still want that leverage.

What I want from C is a clearer view of the machinery underneath that leverage.

When I call a framework method to read a request, something has already accepted a connection, received bytes from a socket, found the boundaries of an HTTP message, allocated memory, parsed text, and decided what to do when the client disappears halfway through. When I write to a database or a file, the operating system is handling buffers, file descriptors, permissions, and failure states that higher-level code turns into a neat function call.

I know these things in theory. I want to know them through practice.

Why this matters for backend work

Backend development is often taught from the framework inward. You learn routes, controllers, models, middleware, and deployment. That is a productive way to start, but it can leave gaps in the mental model.

Those gaps show up when a service becomes slow, memory usage grows unexpectedly, a process hangs, a connection behaves strangely, or a deployment works locally and fails under real traffic. At that point, the abstraction stops being an answer. You need some sense of what it was abstracting.

C forces those hidden decisions back into view.

There is no garbage collector quietly deciding when memory can be reclaimed. A string is not a magical, infinitely safe value. Reading from a socket does not promise to return one complete request. Errors are not automatically turned into exceptions with a useful stack trace. Every boundary needs attention.

That can be uncomfortable, but the discomfort is useful. It trains a more precise kind of thinking: what owns this memory, how long does this value live, what can this call return, what happens if only half the data arrives, and how does the process recover without corrupting its state?

Those questions matter in every backend language, even when the language answers some of them for you.

The path I’m taking

I do not want this to become an endless tour of syntax exercises. The plan is to build small backend-shaped programs and let each one expose a layer of the system.

I’ll start with files, memory, and process basics: reading configuration, parsing input, handling signals, and writing programs that clean up properly when something fails. Then I want to move into networking with a small TCP server, followed by a deliberately limited HTTP/1.1 server.

The point of the HTTP server is not to create a production framework. It is to understand the path a request takes before a framework ever sees it: accepting connections, reading partial input, parsing headers, setting limits, writing valid responses, and deciding how concurrency should work.

From there, I want to explore a few different server models. A process per connection is conceptually simple but expensive. Threads introduce shared state and synchronization. An event loop changes the shape of the program again. Building small versions of each should make their tradeoffs more concrete than reading a comparison ever could.

I also want to connect one of these programs to a real database. Not because C is the most convenient language for CRUD work, but because it will force me to pay attention to connection lifetimes, query boundaries, result ownership, and what cleanup looks like when a request fails halfway through.

Keeping the scope honest

There is an easy trap here: confusing lower-level code with better code.

C gives control, but it also gives you more ways to create security bugs, memory errors, and fragile software. A tiny server that works on my laptop is not evidence that I should replace a mature production stack. If anything, understanding how much care reliable systems require should increase my respect for the tools that already solve these problems well.

So the measure of success is not shipping a C backend to production. It is becoming a better backend developer everywhere else.

I want to read framework behavior with more understanding, make better choices around processes and concurrency, diagnose resource problems with less guesswork, and design APIs with a clearer sense of what the machine is actually doing. I want terms like buffering, backpressure, blocking I/O, and memory ownership to connect to something I have built and debugged—not just something I can define.

Going back to C feels like a way to slow down in the right place. Instead of learning another layer of convenience, I am returning to the layer that makes the convenience possible.

This time, I want to stay with it long enough for the lessons to stick.