Introduction to C
Before writing a single line, it helps to know what C actually is, why it still matters five decades after it was created, and what makes it different from the languages built on top of it.
C is one of the oldest programming languages still in everyday use, and almost every other language you'll ever touch owes something to it. This page walks through what C actually is, where it came from, what kind of language it is, why it's still taught first in most engineering programs, and what its strengths and its sharp edges really are — everything you need before writing your first real line of it.
What is C?
C is a general-purpose programming language for writing software that runs efficiently, moves reasonably well between different machines, and stays close to how the underlying computer actually works. Using C, a programmer describes how data should be stored, how it should be processed, how the program should make decisions, how it should repeat work, how that work should be organized into functions, and how the finished program should interact with the operating environment around it.
Running this program prints a single line: Hello, World! It's a tiny program, but it already touches several ideas — a header, a function called main, a function call, and a return value — that every C program you write will share.
Compared with many of today's mainstream languages, C's own vocabulary is quite small. What it lacks in built-in conveniences, it makes up for with the range of things it can build — from a five-line utility to the core of an operating system.
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n");
return 0;
}Why was C created?
Before C existed, most system-level software was written directly in assembly language. Assembly hands a programmer very fine control over a processor, but that control comes at a cost: every instruction is tied to one specific family of processors, and even simple logic takes many lines to express.
Assembly like this talks directly to registers and processor-specific instructions. It's precise, but it says nothing that transfers cleanly to a different processor family.
The same idea in C reads as ordinary arithmetic. A C compiler is the piece that turns lines like these into whatever instructions the target processor actually needs — the programmer no longer has to think in registers.
C was deliberately positioned between these two extremes, close enough to the hardware to write serious system software, but abstract enough that a human could read, reason about, and maintain the result.
; a rough assembly sketch
MOV R1, #10
ADD R1, R2int a = 10;
int result = a + b;High-level convenience
|
| ( C sits here )
|
Raw machine-level controlA short history of C
C was built at Bell Labs in the early 1970s, with Dennis Ritchie as its principal designer. It didn't appear out of nowhere — it grew out of two earlier, more limited languages, BCPL and then B, refining their ideas into something more capable.
Its first major real-world proving ground was the UNIX operating system, whose developers rewrote large parts of it in C. That mattered enormously: a kernel written in C was dramatically easier to read, fix, and move to new hardware than the equivalent written entirely in assembly, while still being fast enough to serve as an operating system.
C has never stopped evolving. Each numbered standard after the original language added new capabilities on top of what came before, without throwing away the older code that already depended on it — which is a big part of why decades-old C source can often still be compiled today.
BCPL -> B -> C -> C89/C90 -> C99 -> C11 -> C17 -> C23What type of programming language is C?
C gets described with a handful of labels, and each one tells you something concrete about how the language behaves.
General-purpose: C isn't built around one narrow use case. The same language shows up in operating systems, ordinary applications, small utilities, reusable libraries, firmware, compilers, and the low-level pieces of other operating systems.
Procedural: a C program is organized as a set of functions that call one another and act on data, rather than around objects or classes. Here, main is where the program starts, and it simply calls greet to do the actual work.
Statically typed: writing int age = 25; fixes age's type as int for as long as it exists — the same is true of float temperature = 25.5f; and char grade = 'A';. The compiler uses that type information while it checks and translates your program, well before the program ever runs.
Compiled: rather than being read and run line by line, a C program is translated ahead of time into an executable file. The exact shape of that pipeline can differ slightly between toolchains, but this is the general path any C compiler follows.
Case-sensitive: C treats uppercase and lowercase letters as genuinely different characters, so value, Value, and VALUE name three separate, unrelated identifiers — and main and Main are two different names, not the same one written two ways.
#include <stdio.h>
void greet(void)
{
printf("Hello\n");
}
int main(void)
{
greet();
return 0;
}C source code -> compiler -> machine code / object code -> linker -> executableWhy is C important?
C's continued relevance comes down to a small set of qualities it delivers together, which is rarer than it sounds.
Simplicity — the core language itself is compact, with a small set of keywords and rules to internalize before you're writing real programs. Efficiency — a well-written C program can be translated into machine code that runs about as fast as the hardware allows. Portability — the same source, when it avoids anything platform-specific, can often be recompiled for a different processor or operating system with little or no change. Control — C gives you direct access to memory addresses, data layout, and other low-level detail that many newer languages intentionally hide from you. Longevity — decades of accumulated compilers, libraries, tooling, and existing software mean a C program you write today can lean on an enormous, mature ecosystem.
Where is C used?
C's fingerprints are on an unusually wide range of software. System software — operating systems and the software that sits directly on top of them have long leaned on C. Embedded software — devices with limited memory and processing power are frequently programmed in C. Compilers — the tools that translate other languages, and often C itself, are commonly implemented in C. Libraries — performance-sensitive and system-level libraries are routinely written in C. Databases — several database engines rely on C internally. Networking — a great deal of networking infrastructure and the software that supports it is written in C. Applications — C is also perfectly capable of building ordinary desktop and command-line programs.
It's worth keeping the language itself separate from any one field that uses it. Ideas specific to embedded development, like GPIO pins or the SPI protocol, are embedded-systems topics, not part of the C language — they belong in their own dedicated material further along, not here.
What can we do with C?
C programs are assembled from a small set of building blocks, each layered on top of the last: data, held in variables; operations performed on that data; conditions that choose between paths; loops that repeat work; functions that package logic; data structures that organize larger collections; and finally, all of that combined into one complete program.
This short program already exercises several of those building blocks at once, and prints Total = 30.
int marks a, b, and total as a data type — specifically, a whole number.
a and b are variables: named storage holding the values 10 and 20.
= is the assignment operator, storing a value into a variable.
a + b is an expression built from the arithmetic operator +.
printf and main are both functions — printf performs formatted output, and main is where the whole program begins.
return hands a value back from main when the program finishes.
#include <stdio.h>
int main(void)
{
int a = 10;
int b = 20;
int total = a + b;
printf("Total = %d\n", total);
return 0;
}Basic structure of a C program
Nearly every C program you'll see shares the same skeleton, whether it's five lines or five thousand.
At this stage, it's enough to understand each piece conceptually, without worrying yet about exactly how headers, preprocessing, or linking work under the hood — those are each their own topic later on.
int main(void) begins the program's main function — the function every hosted C program starts running from.
The opening { marks the start of that function's body.
printf(...) is a statement requesting formatted output.
return 0; hands a value back from main.
The closing } marks the end of the function body.
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n");
return 0;
}What is a C program?
At its core, a C program is simply a body of C source code, written according to the language's rules, that describes a computation the author wants performed.
Here the programmer is describing a specific sequence of intent: store 10, store 20, add them together, and keep the result. The compiler's job is to turn that description into something the target machine is actually able to carry out.
int a = 10;
int b = 20;
int sum = a + b;Source code and executable code
Writing int result = a + b; produces source code — text meant for a human and a compiler to read, not something a processor can run directly.
The precise steps and tool names shift a little from one platform or toolchain to the next, but this overall shape — source, then compiler, then linker, then a runnable result — is the idea a newcomer to C needs to hold onto first.
programmer -> C source code -> compiler -> object/machine code -> linker -> executable -> executionWhat is a compiler?
A compiler is a program that translates source code from one language into another form — for C, that's typically machine code, or some other lower-level representation meant for further processing. Along the way, it checks that the source actually follows C's rules.
A line like int result = 10 + 20; is easy for a person familiar with C to read. The compiler's job is turning that same line into instructions the target processor can actually execute.
This line is not valid C — there's no value after the equals sign — so a compiler will normally reject it and report an error rather than silently guessing what was meant. Catching mistakes like this before the program ever runs is one of the compiler's most useful jobs.
C code -> compiler -> processor instructionsint x = ;C and the computer
C gives a person a way to describe a computation in terms a human can follow. The compiler's role is turning that description into something the machine underneath can actually carry out, instruction by instruction.
programmer -> C language -> compiler -> executable program -> computer system -> CPU executes instructionsIs C a low-level or high-level language?
C is generally treated as a high-level language, but one that also opens the door to a lot of the detail that most high-level languages deliberately hide — memory addresses chief among them. That's why you'll sometimes hear people call it a "middle-level" language.
That phrase is a useful, informal way of describing where C sits, but it isn't an official category defined anywhere in the C standard itself — think of it as a helpful label, not a technical term.
A more useful mental picture is a line running from high-level abstraction down to low-level machine detail, with C sitting somewhere in the middle, able to reach toward either end depending on how it's used.
Why can C be portable?
Code like int result = a + b; describes an operation without hard-coding any particular processor's instructions into it, which is exactly what makes moving it between machines possible in the first place.
The same source can, in principle, be compiled for very different processor families, with the compiler producing whatever instructions each target actually needs.
That portability has real limits, though. Code that leans on a specific operating system's features, a particular compiler's extensions, or a specific piece of hardware won't automatically carry over — portability is something you have to write for, not something C guarantees for free.
C source
|
+----------+----------+
| | |
x86 ARM RISC-V
| | |
machine machine machine
code code codeC provides control, but also responsibility
C hands the programmer a substantial amount of direct control: explicit data types, pointers, arrays, structures, manual dynamic memory management, and bitwise operations that reach right down to individual bits.
That same control means C expects careful programming in return. Mistakes that other languages catch automatically — memory leaks, invalid memory access, buffer overflows, dangling pointers, and undefined behavior in general — are entirely possible in C, and none of them are prevented for you. Each of these gets its own proper treatment later in this track; for now, the important idea is simply that C's power comes with real responsibility attached.
Advantages of C
Efficient — C is capable of producing software that runs with very little overhead. Portable — with reasonable care, C programs can move between platforms without a rewrite. Structured — functions and other language constructs let even large programs stay organized into manageable pieces. Powerful — C combines higher-level structure with genuinely low-level capability in one language. Mature — decades of compilers, libraries, and tooling stand behind it. Foundational — working through C builds a real understanding of programming fundamentals, how data is represented, how memory works, how compilation happens, and how a computer system fits together underneath it all.
Limitations of C
C also comes with real trade-offs. Memory is managed manually rather than automatically, so the programmer decides exactly when it's allocated and freed. There are fewer built-in safety mechanisms, so the language won't stop every invalid memory operation on your behalf. There's no built-in object-oriented class system — no classes, inheritance, or method dispatch baked into the language itself. And because C gives so much low-level control, it's correspondingly easy to make low-level mistakes.
Writing good C, in practice, comes down to understanding the language well, applying real discipline, testing thoroughly, paying attention to compiler warnings instead of ignoring them, and following established good coding practices.
C compared with higher-level languages
A quick side-by-side against a typical higher-level language, Python, helps place C's design choices in context.
None of this makes one language flatly "better" than the other — C and Python were built to solve different kinds of problems, and each set of trade-offs makes sense for the goals it was designed around.
Feature C Python
--------------------------------------------------------------------------
Abstraction level Relatively low High
Typing Static Dynamic
Memory management Largely programmer-controlled Automatic
Execution Commonly compiled ahead of time Commonly interpreted/VM-based
Hardware-level access Direct and strong Usually indirect
Syntax Compact High-level, expressive
Performance control High, in the programmer's hands More abstracted awayA simple first C program, step by step
One complete, slightly richer example ties everything on this page together.
Running this prints Number = 10, and each line maps directly to something already covered above.
int main(void) defines the main function; for a normal hosted C program, this is where execution is tied to.
int number = 10; declares an object named number with type int, and initializes it with the value 10.
printf("Number = %d\n", number); requests formatted output, with %d specifying that the matching argument, number, should be printed as an integer.
return 0; returns 0 from main — in a hosted environment, returning from main ends the program, and that returned value becomes the program's exit status.
C is a general-purpose, compiled, statically-typed programming language created by Dennis Ritchie at Bell Labs in 1972. It gives you direct control over memory and hardware while still being far more readable and portable than assembly language.
C strikes a rare balance: it's low-level enough to write an operating system kernel or a device driver, yet high-level enough to write it in a way another human can actually read. Learning C also teaches you what's really happening underneath every 'higher-level' language you'll ever use — Python, Java and JavaScript's own runtimes are themselves written in C or C++.
Operating system kernels (Linux, Windows' core, most of macOS), embedded firmware in nearly every microcontroller, database engines (SQLite, parts of PostgreSQL and MySQL), language runtimes (CPython, the JVM), and countless performance-critical libraries.
If a modern language like Python is an automatic car — comfortable, forgiving, does a lot for you — C is a manual car with no traction control. You have direct control over the clutch, the gears and the throttle, which means you can drive it in ways an automatic never could, but it also means there's nothing stopping you from stalling the engine if you're careless.
The smallest complete program This is the smallest complete C program that does something visible: it prints a line of text to the screen and exits cleanly.
int main() { ... } defines the program's entry point — every C program starts executing here, and nowhere else.
printf("Hello, World!\n") calls the printf function with a string literal; \n is the escape sequence for a newline character.
The program runs top to bottom inside main: the printf line executes, sending the text to standard output (your terminal).
return 0; ends main and hands the value 0 back to the operating system — by convention, 0 means 'the program finished successfully'.
Adding a second statement Programs are simply a sequence of statements executed in order, top to bottom — adding a second printf just adds a second line of output, run right after the first.
Execution still begins at main(), exactly as before — adding more code inside it doesn't change where the program starts.
The first printf runs completely (its text reaches the screen) before the second printf even begins — C executes one statement fully before moving to the next.
Both lines end up on the screen, in the same order they were written, because nothing here changes that default top-to-bottom order.
include <stdio.h> int main() { printf("Hi"); } A #include directive needs its leading # — without it, the compiler doesn't recognize it as a preprocessor directive at all. It's also good practice to always end main with an explicit return 0;, even though some compilers tolerate omitting it for main specifically.
C is compiled, not interpreted: your source code is translated all the way to native machine code by a compiler (gcc, clang, MSVC) before it ever runs, which is why C programs typically start up and run faster than interpreted languages.
C is statically typed: every variable's type is fixed at compile time and checked before the program ever runs, catching a whole class of mistakes before you even execute the code.
C has a tiny standard library compared to Python or Java — it deliberately leaves things like networking, GUIs and JSON parsing to third-party libraries, keeping the language's own footprint minimal.
C gives you manual memory management — you decide when memory is allocated and when it's freed, which is powerful but is also the single biggest source of C bugs (covered fully in the Dynamic Memory Allocation topic later in this track).
Assuming C is 'basically the same' as C++ or Java because the syntax looks similar — C has no classes, no exceptions, no built-in strings, no garbage collector, and no function overloading.
Underestimating how much the compiler will let you get away with — C trusts the programmer far more than most modern languages, so mistakes that would be caught immediately elsewhere can silently compile and only fail at runtime, or not fail at all until much later.
Skipping past 'Hello, World' assuming it's trivial — every piece of it (headers, main, return codes) is a concept you'll rely on in every single C program you ever write.
Every embedded systems job posting that mentions 'firmware' or 'RTOS' assumes working C knowledge — this is the direct, practical reason it's still taught first in most engineering curricula.
Reading C is a prerequisite for reading real-world open-source infrastructure — the Linux kernel, Git, Redis, SQLite and Python's own interpreter are all C codebases.
Interviewers frequently use C specifically to test whether a candidate understands memory and pointers at a level that's invisible in garbage-collected languages.
C's philosophy: trust the programmer C was deliberately designed around the principle that the programmer knows what they're doing and the language shouldn't get in their way — no automatic bounds checking on arrays, no automatic memory management, minimal runtime overhead. This is exactly why C can be as fast as hand-written assembly in the right hands, and exactly why it's also the language most associated with security vulnerabilities like buffer overflows.
Standardization: K&R, ANSI C, C99, C11, C17, C23 C isn't one fixed language — it's evolved through standards. K&R C (the original 1978 book) gave way to ANSI C/C89 (1989, the first formal standard), then C99 added // comments and variable declarations mid-block, C11 added multithreading support, and C17/C23 refined things further. Most production code still targets C99 or C11 for maximum compiler compatibility.
Is C a high-level or low-level language?
C is often called a 'middle-level' language: it has high-level constructs like functions, loops and structured control flow, but also gives low-level access to memory addresses and bit-level operations that true high-level languages hide from you.
Why is C still relevant when newer languages exist?
Because operating systems, embedded firmware, and the runtimes of higher-level languages themselves need C's combination of speed, tiny footprint and direct hardware access — nothing else replaces it for those exact use cases, even decades later.
What does 'C has no garbage collector' actually mean for a programmer?
It means memory you allocate with functions like malloc doesn't get cleaned up automatically — you are responsible for calling free() yourself when you're done with it, or the memory leaks for the life of the program.
What is the very first function that runs in any C program?
main() — the C runtime always starts execution there, regardless of how many other functions the program defines.
Name one thing C does NOT provide automatically that Python and Java both do.
Automatic memory management (garbage collection) is the standout example — others include bounds-checked arrays and built-in string objects.
Is C interpreted or compiled?
Compiled — C source code is translated into native machine code by a compiler before the program ever runs.
#include <stdio.h>
int main(void)
{
int number = 10;
printf("Number = %d\n", number);
return 0;
}#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}#include <stdio.h>
int main() {
printf("Hello, World!\n");
printf("This is C.\n");
return 0;
}