10 min read
On this page

Why C

C was created by Dennis Ritchie at Bell Labs in 1972 to rewrite Unix. Over fifty years later, it remains one of the most widely used programming languages in existence. Not because of hype, not because of a slick ecosystem, but because C occupies a unique position: it is close enough to the hardware to write operating systems and close enough to human reasoning to remain productive.

Where C Lives Today

C is not a legacy language. It is the foundation of the software stack you are using right now.

Operating Systems

The Linux kernel is roughly 30 million lines of C. The Windows kernel is written in C (with some C++ and assembly). macOS and iOS run on XNU, a kernel written in C. Android runs on top of Linux. FreeBSD, OpenBSD, NetBSD — all C. Every major operating system is a C program. When people say "systems programming," they primarily mean C.

Databases

PostgreSQL, MySQL, SQLite, Redis — all written in C. When you query a database, the query optimizer, the storage engine, the network protocol handler are all C code. SQLite is the most deployed database in the world and consists of approximately 150,000 lines of C. It ships inside every iPhone, every Android device, every Firefox and Chrome installation, and every Python distribution.

Embedded Systems

The firmware in your car's engine control unit, your router, your thermostat, your pacemaker — C. Embedded systems have limited memory (sometimes as little as 2 KB of RAM), limited processing power, and strict timing requirements. C gives you direct control over all three. There is no room for a garbage collector or a virtual machine on a microcontroller with 16 KB of flash storage.

Compilers & Language Runtimes

The CPython interpreter is written in C. The Ruby MRI interpreter is C. The JVM has significant C/C++ components. Lua, Perl, PHP — all implemented in C. Most languages are implemented in C at some level, which means understanding C helps you understand the language you are actually using. When your Python dict is fast, it is because someone wrote an optimized hash table in C underneath.

Networking

The TCP/IP stack in your operating system is C. OpenSSL and its successors are C. Nginx is C. curl is C. The DNS resolver you use every time you type a URL is C. The protocol implementations that make the internet work are overwhelmingly C.

Games & Graphics

Game engines (parts of Unreal Engine, id Tech), graphics drivers, OpenGL implementations, and GPU compute libraries have deep C roots. When frame time matters, C provides the control to hit performance targets that higher-level languages cannot.

Why Learn C

Understand How Computers Actually Work

High-level languages hide the machine. C exposes it. When you write C, you learn what a pointer is, how memory is laid out, what the stack and heap are, how function calls work, and why certain patterns are fast or slow. This understanding transfers to every other language.

#include <stdio.h>

int main(void) {
    int x = 42;
    int *p = &x;

    printf("Value: %d\n", x);
    printf("Address: %p\n", (void *)p);
    printf("Value via pointer: %d\n", *p);

    return 0;
}
Value: 42
Address: 0x7ffd5e8a3b2c
Value via pointer: 42

This is not abstract. x is four bytes at a specific memory address. p holds that address. *p reads four bytes starting at that address and interprets them as an integer. Every variable in every language works like this underneath — C just lets you see it.

Read Any Systems Codebase

If you can read C, you can read the source code of Linux, PostgreSQL, Redis, Git, Python, Nginx, and thousands of other foundational projects. These codebases are where you learn how real systems are built — not theoretical systems, but production systems handling millions of users. Reading them is how you learn patterns like event loops, memory pools, copy-on-write, and lock-free data structures.

Write Fast Code

C is fast because there is almost nothing between your code and the machine. No runtime type checking. No garbage collector pausing your program. No interpreter overhead. No virtual machine. The compiler translates your C code into machine instructions, and the CPU executes those instructions directly.

The numbers are stark. In the Computer Language Benchmarks Game, C programs typically run within 10-20% of hand-written assembly. Python programs are often 50-100x slower. Java is typically 2-5x slower. When you need to process millions of packets per second or parse gigabytes of data, that difference matters.

// This loop compiles to a handful of machine instructions
void sum_array(const int *arr, size_t n, long *result) {
    long total = 0;
    for (size_t i = 0; i < n; i++) {
        total += arr[i];
    }
    *result = total;
}

The compiler can optimize this aggressively because C has strict aliasing rules and no hidden side effects. The generated code will be close to what you would write in assembly by hand.

Debug & Profile Anything

C gives you direct access to the tools that profile and debug all software, not just C programs. strace shows you system calls. perf shows you CPU hotspots. gdb lets you inspect memory layout byte by byte. When you understand C, you understand the output of these tools, even when debugging programs written in other languages.

#include <stdio.h>
#include <stdlib.h>

// This function is trivial, but you can inspect it with
// gcc -S to see the assembly, objdump to see the binary,
// strace to see the system calls, perf to see CPU cycles
int main(void) {
    int *p = malloc(sizeof(int) * 100);
    if (p == NULL) return 1;
    for (int i = 0; i < 100; i++) p[i] = i * i;
    printf("p[50] = %d\n", p[50]);
    free(p);
    return 0;
}
p[50] = 2500

C vs C++ vs Rust

C vs C++

C++ started as "C with Classes" and grew into an enormous language with templates, exceptions, RAII, smart pointers, lambdas, concepts, modules, and coroutines. C++ gives you higher-level abstractions.

C wins when:

  • Simplicity matters — the C specification is roughly 550 pages; the C++ specification exceeds 1800 pages
  • ABI stability is required — C has a stable, universal ABI; C++ name mangling and vtable layouts differ between compilers
  • The project is a library — nearly every language can call C functions; calling C++ from other languages is painful
  • The team must read each other's code — C has one way to do most things; C++ has five
  • Compilation speed matters — C compiles significantly faster than C++ due to simpler parsing and no template instantiation

The Linux kernel famously does not use C++. Linus Torvalds has argued repeatedly that C++ encourages abstraction where you cannot afford it and hides behavior that kernel developers need to see.

In practice, many large projects use C for their core and expose C APIs even when internal components use C++. SQLite, for example, is pure C with a C API that can be called from any language on any platform.

C vs Rust

Rust is designed to provide memory safety without garbage collection. It prevents use-after-free, double-free, data races, and buffer overflows at compile time through its ownership and borrowing system.

C wins when:

  • You are working in an existing C codebase — rewriting PostgreSQL in Rust is not practical
  • You need maximum portability — C compilers exist for virtually every CPU ever manufactured; Rust targets are more limited
  • You need a stable ABI — Rust does not guarantee ABI stability between compiler versions
  • The ecosystem requires it — Linux kernel development accepts limited Rust but remains overwhelmingly C
  • Compile times matter — C compiles fast; Rust's borrow checker adds significant compilation overhead

Rust is arguably the better choice for new systems projects where memory safety is critical. But C is not going away. The existing C codebase is measured in billions of lines, it runs the world's infrastructure, and someone needs to maintain, extend, and understand it.

Where Each Language Fits

Choose C when you need maximum portability, a stable ABI, or are working in an existing C codebase. Choose C++ when you need C-level performance with higher-level abstractions like RAII, templates, and polymorphism. Choose Rust when you are starting a new project where memory safety is critical and the team can invest in learning the ownership model.

All three have their place; none is universally superior. But if you can only learn one, learn C — because understanding C gives you a foundation for understanding both C++ and Rust, while the reverse is not true.

The Practical Argument

Learning C is not about nostalgia. It is about literacy. A systems programmer who cannot read C is like a doctor who cannot read an X-ray. You might never write a kernel module or a database engine, but understanding C means you understand what your abstractions are abstracting over.

When your Python program is slow, perf and strace will show you what the C runtime and kernel are doing. When your Java application crashes with a segmentation fault in a native library, you need to read C to diagnose it. When you want to understand why a hash table is fast, you need to understand memory layout and pointer arithmetic — concepts that C makes explicit.

C also acts as the lingua franca of programming language interoperability. If you write a library in C, virtually every other language can call it: Python via ctypes or cffi, Java via JNI, Go via cgo, Rust via FFI. No other language has this property. The C ABI is the universal calling convention.

// A library function callable from any language
#include <stddef.h>

// Exported with C linkage — any language can call this
int sum_array(const int *data, size_t length) {
    int total = 0;
    for (size_t i = 0; i < length; i++) {
        total += data[i];
    }
    return total;
}

This function can be compiled into a shared library (.so or .dll) and called from Python, Ruby, Java, Go, Rust, or any other language that supports C FFI. This is not possible with a function written in C++, Java, or Python without significant wrapping.

The C Standard

C has evolved through several standards:

  • C89/C90 — the original ANSI/ISO standard. Still widely supported and used as a baseline.
  • C99 — added bool, // comments, variable-length arrays, stdint.h, designated initializers, and restrict.
  • C11 — added _Atomic, _Thread_local, _Static_assert, and optional features like VLAs becoming optional.
  • C17 — a bugfix release with no new features. The current recommended standard for most projects.
  • C23 — the newest standard, adding typeof, #embed, constexpr, binary literals, and other modern conveniences.

Use -std=c17 for new projects. The language continues to evolve, but it evolves conservatively. Backward compatibility is taken seriously — C code from the 1990s still compiles and runs today.

Common Pitfalls

  • Treating C as a stepping stone — C is not a language you learn on the way to something better. It is a language you learn to understand computing. Rushing through it produces programmers who cannot debug memory issues in any language.
  • Assuming C is obsolete — new C code is written every day. The C17 and C23 standards introduced modern features. Active development continues across operating systems, databases, and embedded systems.
  • Learning C++ first and assuming you know C — C and C++ have diverged significantly. Idiomatic C++ code is not idiomatic C. They emphasize different design philosophies. Moving from C++ to C requires unlearning classes, templates, RAII, and exceptions.
  • Ignoring the ecosystem — C has excellent tooling: Valgrind, AddressSanitizer, UndefinedBehaviorSanitizer, static analyzers like clang-tidy and Coverity, and decades of best practices. The language is "unsafe" only if you ignore the tools.
  • Starting with a complex project — begin with small programs that compile, run, and interact with memory. Understand the fundamentals before attempting networking or multithreading.

Key Takeaways

  • C is the language of operating systems, databases, embedded systems, compilers, and networking infrastructure. It is not legacy — it is foundational.
  • Learning C teaches you how computers work: memory, pointers, the stack, the heap, and the compilation model.
  • C is fast because there is no runtime overhead between your code and the hardware.
  • C has a stable, universal ABI that makes it the lingua franca of system interfaces.
  • C++ offers higher-level abstractions at the cost of complexity. Rust offers memory safety at the cost of a steeper learning curve and less portability. C remains the pragmatic choice for many systems projects.
  • The world runs on C. Understanding it is not optional for anyone who works close to the metal.
  • C's longevity is not an accident. The language evolves conservatively, maintaining backward compatibility while adding modern conveniences. Code written in the 1990s still compiles and runs today.
  • Learning C is an investment that pays dividends in every other language and system you touch.