Published on

Before You Learn Databases, Learn to Think Like a Systems Programmer

Authors
  • avatar
    Name
    Siddharth Singh
    Twitter

There comes a point in every software engineer's career when building another REST API or integrating another framework stops being intellectually satisfying. The abstractions that once accelerated development slowly begin to hide the interesting parts of computing. You know how to use a database, but you no longer want to stop at writing SQL queries. You want to understand what happens after you press Enter. How does that row find its way onto disk? How is it recovered after a crash? Why is one lookup measured in microseconds while another takes milliseconds? Those questions mark the beginning of a very different learning journey.

For many engineers, that journey starts with the idea of building a database. It is an ambitious project, and for good reason. Databases sit at the intersection of almost every area of systems programming. They force you to reason about memory, storage, files, operating systems, concurrency, algorithms, binary formats, caching, and performance, often all within the same piece of code. They are one of the few software projects where every abstraction eventually gives way to raw bytes.

When I started reading about storage engines, I expected to spend my time learning B-Trees, LSM Trees, write-ahead logs, compaction strategies, and indexing algorithms. Those topics certainly matter, but I soon realized they were only the final expressions of much deeper ideas. Every chapter on databases quietly assumes that you already understand how memory is laid out, how binary data is represented, why CPUs prefer contiguous memory, why file formats evolve, why iterators are everywhere, and why a single unnecessary allocation can matter. Without those foundations, it is easy to understand what a database is doing while never fully appreciating why it was designed that way.

That observation is what inspired this series.

This is not a series about implementing a database. At least, not yet.

Instead, it is a series about learning the language of systems programming. The goal is to build the intuition that experienced infrastructure engineers carry with them regardless of whether they are working on a database, a search engine, a network protocol, an AI inference runtime, a file system, or an operating system. Once you begin to recognize the underlying patterns, you start noticing that these seemingly unrelated systems are solving remarkably similar problems. They all move bytes. They all manage memory. They all trade simplicity for performance in carefully chosen places. They all spend an enormous amount of effort minimizing unnecessary work.

One of the biggest shifts you'll experience is learning to think in terms of data rather than objects. Application developers naturally describe software using classes, interfaces, and business entities because those abstractions make applications easier to build. Systems programmers tend to think differently. They ask where the data lives, how it is represented, how many bytes it occupies, how often it moves through memory, whether it can be skipped without decoding it, and what happens when millions of those records are processed every second. Neither perspective is better than the other, but they optimize for very different goals.

This series is an attempt to bridge those two worlds.

Every article will begin with a fundamental concept rather than an implementation. We will explore why the concept exists, where it appears in real systems, and what engineering trade-offs it addresses. Only after building that intuition will we write Rust code—not large projects or production-ready libraries, but focused examples that isolate one idea at a time. The objective is not to impress you with clever code. It is to make each concept feel obvious by the time you finish reading.

At the end of every article, you'll find a few small exercises. They won't resemble interview problems or competitive programming challenges. Instead, they'll ask you to perform the kinds of experiments that systems programmers do every day. You might redesign a binary file format, measure the effect of struct padding, benchmark two approaches to reading a file, or implement a small iterator over a custom data structure. None of these exercises are large enough to become side projects, but together they will help develop the intuition that is difficult to gain from reading alone.

I'll be using Rust throughout the series because it exposes many systems programming concepts naturally. Ownership, slices, iterators, lifetimes, and memory safety are not distractions from the topic—they are part of it. More importantly, Rust allows us to work close to the hardware while still benefiting from modern tooling and strong compile-time guarantees. Even if your primary language is C++, Go, Zig, or C#, the principles we discuss will transfer almost directly.

Here is the roadmap I have in mind.

Part 1 — Thinking in Bytes Instead of Objects

Why systems programmers see bytes where application developers see objects, and how that shift in perspective changes everything that follows.

Part 2 — Memory Layouts Every Systems Programmer Should Understand

How alignment, padding, locality, and data layout influence both correctness and performance.

Part 3 — Endianness and Binary Representation

A practical look at how numbers become bytes, why byte order exists, and why it still matters today.

Part 4 — Designing Binary File Formats

How real systems store data on disk, evolve file formats over time, and recover from corruption.

Part 5 — File I/O Beyond Read and Write

Buffers, seeking, random access, streaming, and the operating system's role in moving data.

Part 6 — Why Iterators Are Everywhere

A concept that quietly connects databases, storage engines, compilers, and countless other systems.

Part 7 — Collections That Scale

Not every data structure deserves a place in systems software. We'll examine the ones that do and, more importantly, why.

Part 8 — Cache Locality

One of the most important performance concepts that many developers never encounter until they begin writing low-level software.

Part 9 — Zero-Copy Programming

How modern systems reduce allocations and unnecessary data movement.

Part 10 — Parsing Binary Data

Building reliable and efficient parsers without depending on heavy abstractions.

Part 11 — Serialization Beyond JSON

A tour of the formats that power high-performance systems and the trade-offs each one makes.

Part 12 — Essential Data Structures

Bloom Filters, Skip Lists, LRU caches, ring buffers, and the engineering problems they were invented to solve.

Part 13 — Measuring Performance

Because optimization without measurement is usually just speculation.

Part 14 — Testing Systems Software

From unit tests to property testing and fuzzing, we'll look at how infrastructure code earns trust.

Part 15 — Concurrency Fundamentals

Threads, synchronization, immutability, and the patterns that appear repeatedly across systems software.

If you've picked up this series because you want to build a database one day, I hope these articles make that journey less intimidating. If you never build one, I believe the ideas are still worth learning. They will help you understand software that sits beneath the frameworks we use every day, and they will change the way you think about performance, data, and abstraction.

The goal isn't simply to become a better database engineer. It's to become the kind of engineer who is comfortable reading unfamiliar systems code, reasoning about performance without guessing, and understanding what happens beneath the abstractions that modern software is built upon.

Everything else begins with that way of thinking.