Language lab · Fundamentals
🔧 C
Concept → trace → write code → run tests → explain why → improve → mini-project. Start in the browser if you like; set up your own computer when you are ready.
📖 Topics with real code (38)
Every verified output below was produced by running the code with gcc (GCC) 14.4.0 on 6 Nov 2026. Open a topic to edit the code and run it right here in your browser.
🌱 Beginner (10)
Variables and data types
C types have a fixed size in memory: int, long, float, double, char, and bool from stdbool.h. printf needs the right format: %d int, %ld long, %f double, %c char. sizeof shows the bytes.
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
int main(void) {
int age = 15;
long population = 1428000000L;
double height = 1.62;
bool is_student = true;
char grade = 'A';
const char *name = "Asha";
printf("%s %d %.2f %d %c\n", name, age, height, is_student, grade);
printf("population %ld\n", population);
printf("sizes: int=%zu double=%zu char=%zu bool=%zu\n", sizeof(int), sizeof(double), sizeof(char), sizeof(bool));
int marks = atoi("88") + 2;
double avg = (double)7 / 2;
printf("%d %.1f %d %d\n", marks, avg, 7 / 2, (int)9.99);
return 0;
}
✅ Verified output
Asha 15 1.62 1 A population 1428000000 sizes: int=4 double=8 char=1 bool=1 90 3.5 3 9
Operators and boolean logic
In C, 0 is false and any other number is true. && and || short-circuit. Integer division drops the fraction; % is the remainder; bitwise & | ^ << work on bits.
#include <stdio.h>
int main(void) {
int a = 17, b = 5;
printf("%d %d %d %d %d\n", a + b, a - b, a * b, a / b, a % b);
int has_ticket = 1, is_vip = 0;
printf("enter: %d either: %d not: %d\n", has_ticket && !is_vip, has_ticket || is_vip, !has_ticket);
printf("6&3=%d 6|3=%d 6^3=%d 1<<4=%d\n", 6 & 3, 6 | 3, 6 ^ 3, 1 << 4);
int x = 5;
x += 3; x *= 2; x++;
printf("x=%d, 10 > 3 is %d\n", x, 10 > 3);
return 0;
}
✅ Verified output
22 12 85 3 2 enter: 1 either: 1 not: 0 6&3=2 6|3=7 6^3=5 1<<4=16 x=17, 10 > 3 is 1
Conditionals: if, else, switch
if/else picks a branch; switch jumps to a matching case and needs break to stop falling through; ?: is a short if/else.
#include <stdio.h>
int main(void) {
int marks = 78;
const char *grade;
if (marks >= 90) grade = "A+";
else if (marks >= 75) grade = "A";
else if (marks >= 35) grade = "Pass";
else grade = "Fail";
printf("Grade: %s\n", grade);
int day = 6; /* 0 = Sunday */
switch (day) {
case 0:
case 6:
printf("Weekend\n");
break;
default:
printf("School day\n");
}
printf("%s\n", marks >= 35 ? "passed" : "try again");
return 0;
}
✅ Verified output
Grade: A Weekend passed
Loops: for, while, do-while
for counts, while checks first, do-while checks last so it runs at least once. break and continue control the loop.
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 5; i++) {
if (i == 3) continue;
printf("for %d\n", i);
}
int n = 10;
while (n > 0) {
if (n < 4) break;
printf("while %d\n", n);
n -= 3;
}
int k = 100;
do { printf("do-while runs once: %d\n", k); } while (k < 0);
for (int r = 1; r <= 3; r++) {
for (int c = 1; c <= 3; c++) printf("%3d", r * c);
printf("\n");
}
return 0;
}
✅ Verified output
for 1 for 2 for 4 for 5 while 10 while 7 while 4 do-while runs once: 100 1 2 3 2 4 6 3 6 9
Functions and pass-by-value
A function needs a declared return type and parameter types. C passes copies of values, so a function cannot change the caller's variable unless it gets a pointer.
#include <stdio.h>
int area(int length, int width) { return length * width; }
double average(const int nums[], int count) {
int sum = 0;
for (int i = 0; i < count; i++) sum += nums[i];
return (double)sum / count;
}
void try_change(int v) { v = 99; }
void really_change(int *v) { *v = 99; }
int main(void) {
int nums[] = {4, 9, 2, 7};
printf("area %d, average %.2f\n", area(5, 3), average(nums, 4));
int x = 1;
try_change(x);
printf("after copy: %d\n", x);
really_change(&x);
printf("after pointer: %d\n", x);
return 0;
}
✅ Verified output
area 15, average 5.50 after copy: 1 after pointer: 99
Integer sizes, limits and overflow
Each integer type has a fixed size. Going past the maximum of an unsigned type wraps around; for signed types it is undefined behaviour, so check before adding.
#include <stdio.h>
#include <limits.h>
#include <stdint.h>
int main(void) {
printf("char %zu, short %zu, int %zu, long long %zu bytes\n", sizeof(char), sizeof(short), sizeof(int), sizeof(long long));
printf("INT_MAX = %d, INT_MIN = %d\n", INT_MAX, INT_MIN);
unsigned char u = 250;
u += 10; /* wraps: 260 - 256 */
printf("unsigned char 250 + 10 = %u\n", u);
int a = INT_MAX, b = 1;
if (a > INT_MAX - b) printf("a + b would overflow: refused\n");
uint32_t big = 4000000000u;
printf("uint32_t holds %u, int64_t holds %lld\n", big, (long long)INT64_MAX);
return 0;
}
✅ Verified output
char 1, short 2, int 4, long long 8 bytes INT_MAX = 2147483647, INT_MIN = -2147483648 unsigned char 250 + 10 = 4 a + b would overflow: refused uint32_t holds 4000000000, int64_t holds 9223372036854775807
Booleans with stdbool.h
C has no built-in true/false keywords before C23; include stdbool.h. Any non-zero value counts as true in a condition.
#include <stdio.h>
#include <stdbool.h>
bool is_leap(int y) { return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0; }
int main(void) {
int years[] = {1900, 2000, 2024, 2025};
for (int i = 0; i < 4; i++)
printf("%d leap? %s\n", years[i], is_leap(years[i]) ? "true" : "false");
printf("!0 = %d, !5 = %d, 3 && 0 = %d, 3 || 0 = %d\n", !0, !5, 3 && 0, 3 || 0);
return 0;
}
✅ Verified output
1900 leap? false 2000 leap? true 2024 leap? true 2025 leap? false !0 = 1, !5 = 0, 3 && 0 = 0, 3 || 0 = 1
printf and scanf formats
printf prints with format codes (%d int, %f float, %c char, %s string, %x hex). Widths and precision line up tables. sscanf reads from a string the same way scanf reads input.
#include <stdio.h>
int main(void) {
printf("|%5d|%-5d|%05d|\n", 42, 42, 42);
printf("|%.2f|%8.3f|%e|\n", 3.14159, 2.5, 123456.0);
printf("|%c|%s|%x|%o|%%|\n", 'A', "GayaGnan", 255, 8);
int day, month, year;
char name[20];
if (sscanf("Asha 15/08/2025", "%19s %d/%d/%d", name, &day, &month, &year) == 4)
printf("%s: day %d, month %d, year %d\n", name, day, month, year);
return 0;
}
✅ Verified output
| 42|42 |00042| |3.14| 2.500|1.234560e+05| |A|GayaGnan|ff|10|%| Asha: day 15, month 8, year 2025
Nested loops: patterns and tables
A loop inside a loop runs fully for each outer turn: perfect for grids, multiplication tables and star patterns.
#include <stdio.h>
int main(void) {
for (int row = 1; row <= 4; row++) {
for (int s = 0; s < 4 - row; s++) putchar(' ');
for (int k = 0; k < 2 * row - 1; k++) putchar('*');
putchar('\n');
}
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 5; j++) printf("%4d", i * j);
printf("\n");
}
return 0;
}
✅ Verified output
* *** ***** ******* 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15
break, continue and loop labels with goto
break leaves the nearest loop, continue skips to the next turn. To leave two loops at once, use a flag (or, rarely, goto to a clean-up label).
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 10; i++) {
if (i % 3 == 0) continue;
if (i > 8) break;
printf("%d ", i);
}
printf("\n");
int grid[3][3] = {{1, 2, 3}, {4, 42, 6}, {7, 8, 9}};
int found = 0;
for (int r = 0; r < 3 && !found; r++)
for (int c = 0; c < 3; c++)
if (grid[r][c] == 42) { printf("42 at row %d col %d\n", r, c); found = 1; break; }
return 0;
}
✅ Verified output
1 2 4 5 7 8 42 at row 1 col 1
🌿 Intermediate (16)
Arrays and sorting
An array is a fixed block of same-type values. Its size is sizeof(arr)/sizeof(arr[0]). qsort sorts with a compare function. C does not check bounds: never index past the end.
#include <stdio.h>
#include <stdlib.h>
int cmp(const void *a, const void *b) { return (*(const int *)a - *(const int *)b); }
int main(void) {
int marks[] = {72, 95, 64, 88, 91};
size_t n = sizeof(marks) / sizeof(marks[0]);
qsort(marks, n, sizeof(int), cmp);
printf("sorted:");
for (size_t i = 0; i < n; i++) printf(" %d", marks[i]);
printf("\nfirst %d last %d count %zu\n", marks[0], marks[n - 1], n);
int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
int total = 0;
for (int r = 0; r < 2; r++) for (int c = 0; c < 3; c++) total += grid[r][c];
printf("2D total %d\n", total);
return 0;
}
✅ Verified output
sorted: 64 72 88 91 95 first 64 last 95 count 5 2D total 21
Strings (char arrays)
A C string is an array of char ending with '\0'. Use string.h: strlen, strcpy/strncpy, strcat, strcmp. snprintf formats safely into a buffer of known size.
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main(void) {
char name[32] = "GayaGnan";
printf("%s has %zu letters\n", name, strlen(name));
strncat(name, " labs", sizeof(name) - strlen(name) - 1);
printf("%s\n", name);
for (size_t i = 0; name[i]; i++) name[i] = (char)toupper((unsigned char)name[i]);
printf("%s, compare: %d\n", name, strcmp("apple", "banana") < 0);
char buf[40];
snprintf(buf, sizeof buf, "Total: %.2f for %d items", 49.5 * 3, 3);
printf("%s\n", buf);
const char *w = "level";
int pal = 1;
for (size_t i = 0, j = strlen(w) - 1; i < j; i++, j--) if (w[i] != w[j]) pal = 0;
printf("palindrome: %d\n", pal);
return 0;
}
✅ Verified output
GayaGnan has 8 letters GayaGnan labs GAYAGNAN LABS, compare: 1 Total: 148.50 for 3 items palindrome: 1
Structs and arrays of structs
struct groups related fields into one type. typedef gives it a short name. Pass a pointer to a struct to change it; use -> to reach fields through a pointer.
#include <stdio.h>
#include <string.h>
typedef struct {
char name[20];
int marks;
} Student;
void bonus(Student *s, int extra) { s->marks += extra; }
int main(void) {
Student cls[] = {{"Asha", 88}, {"Ravi", 95}, {"Meena", 72}};
int n = sizeof cls / sizeof cls[0];
bonus(&cls[2], 5);
int best = 0;
for (int i = 0; i < n; i++) {
printf("%-6s %d\n", cls[i].name, cls[i].marks);
if (cls[i].marks > cls[best].marks) best = i;
}
printf("top: %s, struct size %zu bytes\n", cls[best].name, sizeof(Student));
return 0;
}
✅ Verified output
Asha 88 Ravi 95 Meena 77 top: Ravi, struct size 24 bytes
Dynamic memory: malloc, realloc, free
malloc asks the heap for memory at run time; realloc grows it; free gives it back. Always check for NULL and free exactly once.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int cap = 2, len = 0;
int *list = malloc(cap * sizeof *list);
if (!list) return 1;
for (int v = 10; v <= 50; v += 10) {
if (len == cap) {
cap *= 2;
int *bigger = realloc(list, cap * sizeof *list);
if (!bigger) { free(list); return 1; }
list = bigger;
printf("grew to %d\n", cap);
}
list[len++] = v;
}
for (int i = 0; i < len; i++) printf("%d ", list[i]);
printf("\nlen %d cap %d\n", len, cap);
free(list);
list = NULL; /* avoid a dangling pointer */
return 0;
}
✅ Verified output
grew to 4 grew to 8 10 20 30 40 50 len 5 cap 8
Error handling with return codes and errno
C has no exceptions. Functions return a status, and library calls set errno. strtol with an end pointer tells you whether the whole text was a number.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int parse_int(const char *s, long *out) {
char *end;
errno = 0;
long v = strtol(s, &end, 10);
if (errno || *end != '\0' || end == s) return -1;
*out = v;
return 0;
}
int main(void) {
const char *inputs[] = {"42", "abc", "12x"};
for (int i = 0; i < 3; i++) {
long v;
if (parse_int(inputs[i], &v) == 0) printf("%s -> %ld\n", inputs[i], v);
else printf("%s -> not a number\n", inputs[i]);
}
FILE *f = fopen("/no/such/dir/file.txt", "r");
if (!f) printf("fopen failed: %s\n", strerror(errno));
return 0;
}
✅ Verified output
42 -> 42 abc -> not a number 12x -> not a number fopen failed: No such file or directory
Files: fprintf and fgets
fopen opens a file ("w" write, "r" read). fprintf writes formatted text; fgets reads a line at a time. Always fclose.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
const char *path = "/tmp/marks.csv";
FILE *f = fopen(path, "w");
if (!f) return 1;
fprintf(f, "name,marks\nAsha,88\nRavi,95\n");
fclose(f);
f = fopen(path, "r");
char line[64];
int total = 0, count = 0;
fgets(line, sizeof line, f); /* skip header */
while (fgets(line, sizeof line, f)) {
char *name = strtok(line, ",");
int marks = atoi(strtok(NULL, ",\n"));
printf("%s -> %d\n", name, marks);
total += marks; count++;
}
fclose(f);
remove(path);
printf("average %.1f\n", (double)total / count);
return 0;
}
✅ Verified output
Asha -> 88 Ravi -> 95 average 91.5
2D arrays and matrix multiplication
A 2D array is rows of columns stored one after another in memory. Pass it to a function with the column count in the type.
#include <stdio.h>
#define N 3
void mul(int a[N][N], int b[N][N], int out[N][N]) {
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++) {
out[i][j] = 0;
for (int k = 0; k < N; k++) out[i][j] += a[i][k] * b[k][j];
}
}
int main(void) {
int a[N][N] = {{1, 2, 0}, {0, 1, 0}, {2, 0, 1}}, id[N][N] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}, c[N][N];
mul(a, a, c);
for (int i = 0; i < N; i++) printf("%d %d %d\n", c[i][0], c[i][1], c[i][2]);
mul(a, id, c);
printf("A x I == A? %s\n", c[2][0] == 2 && c[0][1] == 2 ? "yes" : "no");
return 0;
}
✅ Verified output
1 4 0 0 1 0 4 4 1 A x I == A? yes
String library: strlen, strcpy, strcmp, strtok
string.h works on NUL-terminated char arrays. Always leave room for the final '\0'; prefer snprintf and strncpy-style limits to avoid overflows.
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main(void) {
char s[64];
snprintf(s, sizeof s, "%s", "maths,science,english");
printf("length %zu\n", strlen(s));
printf("compare: %d %d\n", strcmp("apple", "banana") < 0, strcmp("same", "same"));
for (char *tok = strtok(s, ","); tok; tok = strtok(NULL, ",")) {
tok[0] = (char)toupper((unsigned char)tok[0]);
printf("[%s]", tok);
}
printf("\n%s\n", strstr("GayaGnan Labs", "Gnan"));
return 0;
}
✅ Verified output
length 21 compare: 1 0 [Maths][Science][English] Gnan Labs
Enums and switch state machine
An enum names a set of integer constants. A switch on the current state plus an event is a simple, testable state machine.
#include <stdio.h>
typedef enum { RED, GREEN, YELLOW } Light;
const char *names[] = {"RED", "GREEN", "YELLOW"};
Light next(Light s) {
switch (s) {
case RED: return GREEN;
case GREEN: return YELLOW;
case YELLOW: return RED;
}
return RED;
}
int main(void) {
Light s = RED;
int secs[] = {30, 25, 5};
for (int i = 0; i < 6; i++) {
printf("%-6s for %2d s\n", names[s], secs[s]);
s = next(s);
}
return 0;
}
✅ Verified output
RED for 30 s GREEN for 25 s YELLOW for 5 s RED for 30 s GREEN for 25 s YELLOW for 5 s
Unions and bit fields
A union stores different types in the same memory. Bit fields pack several small values into one integer, as in hardware registers.
#include <stdio.h>
#include <stdint.h>
typedef struct {
unsigned ready : 1;
unsigned error : 1;
unsigned mode : 2;
unsigned count : 4;
} Status;
typedef union { float f; uint32_t bits; } FloatBits;
int main(void) {
Status st = {1, 0, 2, 9};
printf("ready=%u error=%u mode=%u count=%u (size %zu bytes)\n", st.ready, st.error, st.mode, st.count, sizeof st);
FloatBits fb = {.f = 1.0f};
printf("1.0f is stored as 0x%08X\n", fb.bits);
fb.f = -2.5f;
printf("-2.5f sign bit = %u\n", fb.bits >> 31);
return 0;
}
✅ Verified output
ready=1 error=0 mode=2 count=9 (size 4 bytes) 1.0f is stored as 0x3F800000 -2.5f sign bit = 1
Linked list: insert, search, delete
Each node points to the next. Inserting or removing in the middle only changes pointers; free every node when done.
#include <stdio.h>
#include <stdlib.h>
typedef struct Node { int val; struct Node *next; } Node;
Node *push(Node *head, int v) { Node *n = malloc(sizeof *n); n->val = v; n->next = head; return n; }
Node *removeVal(Node *head, int v) {
Node **pp = &head;
while (*pp && (*pp)->val != v) pp = &(*pp)->next;
if (*pp) { Node *dead = *pp; *pp = dead->next; free(dead); }
return head;
}
void print(const Node *n) { for (; n; n = n->next) printf("%d -> ", n->val); printf("NULL\n"); }
int main(void) {
Node *list = NULL;
for (int i = 1; i <= 5; i++) list = push(list, i * 10);
print(list);
list = removeVal(list, 30);
list = removeVal(list, 50);
print(list);
while (list) { Node *n = list->next; free(list); list = n; }
return 0;
}
✅ Verified output
50 -> 40 -> 30 -> 20 -> 10 -> NULL 40 -> 20 -> 10 -> NULL
Stack and queue with arrays
A stack pushes and pops at one end (LIFO). A circular queue reuses its array by wrapping the indices with %.
#include <stdio.h>
#define CAP 4
int stack[CAP], top = 0;
int q[CAP], head = 0, tail = 0, size = 0;
int enqueue(int v) { if (size == CAP) return 0; q[tail] = v; tail = (tail + 1) % CAP; size++; return 1; }
int dequeue(int *v) { if (!size) return 0; *v = q[head]; head = (head + 1) % CAP; size--; return 1; }
int main(void) {
for (int i = 1; i <= 3; i++) stack[top++] = i;
while (top) printf("pop %d\n", stack[--top]);
for (int i = 1; i <= 5; i++) if (!enqueue(i)) printf("queue full, %d waits\n", i);
int v;
dequeue(&v); printf("served %d\n", v);
enqueue(5);
while (dequeue(&v)) printf("served %d\n", v);
return 0;
}
✅ Verified output
pop 3 pop 2 pop 1 queue full, 5 waits served 1 served 2 served 3 served 4 served 5
Binary search tree
Smaller keys go left, larger go right, so search, insert and in-order (sorted) printing follow the tree's shape.
#include <stdio.h>
#include <stdlib.h>
typedef struct T { int key; struct T *l, *r; } T;
T *insert(T *t, int k) {
if (!t) { t = calloc(1, sizeof *t); t->key = k; return t; }
if (k < t->key) t->l = insert(t->l, k); else t->r = insert(t->r, k);
return t;
}
int height(T *t) { if (!t) return 0; int a = height(t->l), b = height(t->r); return 1 + (a > b ? a : b); }
void inorder(T *t) { if (!t) return; inorder(t->l); printf("%d ", t->key); inorder(t->r); }
void freeTree(T *t) { if (!t) return; freeTree(t->l); freeTree(t->r); free(t); }
int main(void) {
int keys[] = {50, 30, 70, 20, 40, 60, 80, 35};
T *root = NULL;
for (int i = 0; i < 8; i++) root = insert(root, keys[i]);
inorder(root);
printf("\nheight %d\n", height(root));
freeTree(root);
return 0;
}
✅ Verified output
20 30 35 40 50 60 70 80 height 4
qsort and bsearch with comparators
The C library sorts and searches any array if you give it a comparison function that returns negative, zero or positive.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct { char name[12]; int marks; } Student;
int byMarksDesc(const void *a, const void *b) { return ((const Student *)b)->marks - ((const Student *)a)->marks; }
int cmpInt(const void *a, const void *b) { int x = *(const int *)a, y = *(const int *)b; return (x > y) - (x < y); }
int main(void) {
Student s[] = {{"Asha", 91}, {"Ravi", 78}, {"Meena", 85}, {"Kiran", 65}};
qsort(s, 4, sizeof s[0], byMarksDesc);
for (int i = 0; i < 4; i++) printf("%d. %-6s %d\n", i + 1, s[i].name, s[i].marks);
int a[] = {42, 7, 19, 3, 88, 21};
qsort(a, 6, sizeof a[0], cmpInt);
int key = 19, *hit = bsearch(&key, a, 6, sizeof a[0], cmpInt);
printf("19 found at index %td\n", hit - a);
return 0;
}
✅ Verified output
1. Asha 91 2. Meena 85 3. Ravi 78 4. Kiran 65 19 found at index 2
Preprocessor: macros and conditional compilation
#define makes constants and macros; parenthesise macro arguments. #ifdef includes code only when a symbol is defined, which is how debug logging is switched on and off.
#include <stdio.h>
#define SQUARE_BAD(x) x * x
#define SQUARE(x) ((x) * (x))
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
#define DEBUG 1
#if DEBUG
#define LOG(msg) printf("[debug %s:%d] %s\n", __func__, __LINE__, msg)
#else
#define LOG(msg)
#endif
int main(void) {
printf("SQUARE_BAD(2+3) = %d, SQUARE(2+3) = %d\n", SQUARE_BAD(2 + 3), SQUARE(2 + 3));
int marks[] = {91, 78, 85, 65, 70};
printf("%zu marks\n", ARRAY_LEN(marks));
LOG("finished counting");
return 0;
}
✅ Verified output
SQUARE_BAD(2+3) = 11, SQUARE(2+3) = 25 5 marks [debug main:15] finished counting
const, static and scope
const promises not to change a value. A static local keeps its value between calls; a static global is private to its file.
#include <stdio.h>
static int calls = 0; /* visible only in this file */
int next_ticket(void) {
static int ticket = 100; /* kept between calls */
calls++;
return ++ticket;
}
double average(const int *xs, int n) { /* const: this function will not change xs */
long sum = 0;
for (int i = 0; i < n; i++) sum += xs[i];
return (double)sum / n;
}
int main(void) {
for (int i = 0; i < 3; i++) printf("ticket %d\n", next_ticket());
const int marks[] = {91, 78, 85};
printf("average %.2f after %d calls\n", average(marks, 3), calls);
return 0;
}
✅ Verified output
ticket 101 ticket 102 ticket 103 average 84.67 after 3 calls
🌳 Advanced (12)
Recursion and memoization
A recursive function calls itself until a base case. A static array caches results, so fib(80) is instant; unsigned long long holds the large value.
#include <stdio.h>
unsigned long long memo[100];
unsigned long long fib(int n) {
if (n < 2) return (unsigned long long)n;
if (memo[n]) return memo[n];
return memo[n] = fib(n - 1) + fib(n - 2);
}
int binary_search(const int a[], int lo, int hi, int target) {
if (lo > hi) return -1;
int mid = lo + (hi - lo) / 2;
if (a[mid] == target) return mid;
return a[mid] < target ? binary_search(a, mid + 1, hi, target) : binary_search(a, lo, mid - 1, target);
}
int main(void) {
for (int i = 0; i < 10; i++) printf("%llu ", fib(i));
printf("| fib(80) = %llu\n", fib(80));
int a[] = {2, 5, 8, 12, 16, 23, 38};
printf("index of 23: %d\n", binary_search(a, 0, 6, 23));
return 0;
}
✅ Verified output
0 1 1 2 3 5 8 13 21 34 | fib(80) = 23416728348467685 index of 23: 5
Function pointers and callbacks
A function pointer stores which function to call. Passing one lets a function such as apply or qsort run your code: C's version of higher-order functions.
#include <stdio.h>
typedef int (*op_t)(int, int);
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int apply_all(const int *v, int n, int start, op_t op) {
int acc = start;
for (int i = 0; i < n; i++) acc = op(acc, v[i]);
return acc;
}
void for_each(int *v, int n, void (*fn)(int *)) { for (int i = 0; i < n; i++) fn(&v[i]); }
void triple(int *x) { *x *= 3; }
int main(void) {
int v[] = {1, 2, 3, 4};
op_t ops[] = {add, mul};
const char *names[] = {"sum", "product"};
for (int i = 0; i < 2; i++) printf("%s = %d\n", names[i], apply_all(v, 4, i, ops[i]));
for_each(v, 4, triple);
printf("tripled: %d %d %d %d\n", v[0], v[1], v[2], v[3]);
return 0;
}
✅ Verified output
sum = 10 product = 24 tripled: 3 6 9 12
Threads with pthreads and a mutex
Each thread runs a function at the same time. Without the mutex, two threads could update total together and lose a count; with it the answer is always 4000.
#include <stdio.h>
#include <pthread.h>
long total = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *worker(void *arg) {
int id = *(int *)arg;
for (int i = 0; i < 1000; i++) {
pthread_mutex_lock(&lock);
total++;
pthread_mutex_unlock(&lock);
}
return (void *)(long)id;
}
int main(void) {
pthread_t t[4];
int ids[4] = {1, 2, 3, 4};
for (int i = 0; i < 4; i++) pthread_create(&t[i], NULL, worker, &ids[i]);
for (int i = 0; i < 4; i++) {
void *ret;
pthread_join(t[i], &ret);
printf("thread %ld done\n", (long)ret);
}
printf("total %ld\n", total);
return 0;
}
✅ Verified output
thread 1 done thread 2 done thread 3 done thread 4 done total 4000
Pointers and pointer arithmetic
A pointer holds a memory address. *p reads the value there; p + 1 moves to the next element (by the size of the type). Arrays decay to a pointer to their first element.
#include <stdio.h>
void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
int main(void) {
int arr[] = {10, 20, 30, 40};
int *p = arr; /* same as &arr[0] */
printf("*p=%d *(p+2)=%d p[3]=%d\n", *p, *(p + 2), p[3]);
printf("elements apart: %td\n", &arr[3] - &arr[0]);
int sum = 0;
for (int *q = arr; q < arr + 4; q++) sum += *q;
printf("sum via pointer walk %d\n", sum);
int x = 1, y = 2;
swap(&x, &y);
printf("swapped x=%d y=%d\n", x, y);
int **pp = &p;
printf("pointer to pointer: %d\n", **pp);
return 0;
}
✅ Verified output
*p=10 *(p+2)=30 p[3]=40 elements apart: 3 sum via pointer walk 100 swapped x=2 y=1 pointer to pointer: 10
Bitwise operations and masks
& | ^ ~ << >> work on bits: set, clear, toggle and test flags, count set bits, and check for a power of two.
#include <stdio.h>
#include <stdint.h>
#define BIT(n) (1u << (n))
int popcount(uint32_t x) { int c = 0; while (x) { x &= x - 1; c++; } return c; }
int main(void) {
uint8_t reg = 0;
reg |= BIT(0) | BIT(3); /* set */
reg ^= BIT(1); /* toggle */
reg &= (uint8_t)~BIT(0); /* clear */
printf("reg = 0x%02X, bit3 is %s\n", reg, (reg & BIT(3)) ? "on" : "off");
printf("popcount(0xF0F0) = %d\n", popcount(0xF0F0));
for (unsigned n = 60; n <= 64; n++) printf("%u%s ", n, (n & (n - 1)) == 0 ? "(pow2)" : "");
printf("\nswap nibbles of 0xAB -> 0x%X\n", ((0xAB & 0x0F) << 4) | (0xAB >> 4));
return 0;
}
✅ Verified output
reg = 0x0A, bit3 is on popcount(0xF0F0) = 8 60 61 62 63 64(pow2) swap nibbles of 0xAB -> 0xBA
Hash table with chaining
A hash function picks a bucket for each key; keys that collide are kept in a short linked list inside that bucket.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUCKETS 8
typedef struct E { char key[16]; int val; struct E *next; } E;
E *table[BUCKETS];
unsigned hash(const char *s) { unsigned h = 5381; while (*s) h = h * 33 + (unsigned char)*s++; return h % BUCKETS; }
void put(const char *k, int v) {
unsigned b = hash(k);
for (E *e = table[b]; e; e = e->next) if (!strcmp(e->key, k)) { e->val = v; return; }
E *e = malloc(sizeof *e); snprintf(e->key, sizeof e->key, "%s", k); e->val = v; e->next = table[b]; table[b] = e;
}
int get(const char *k, int *v) { for (E *e = table[hash(k)]; e; e = e->next) if (!strcmp(e->key, k)) { *v = e->val; return 1; } return 0; }
int main(void) {
const char *names[] = {"asha", "ravi", "meena", "kiran", "zara", "dev"};
for (int i = 0; i < 6; i++) put(names[i], 60 + i * 7);
put("ravi", 99);
int v;
if (get("ravi", &v)) printf("ravi -> %d\n", v);
printf("bob found? %d\n", get("bob", &v));
for (int b = 0; b < BUCKETS; b++) { int n = 0; for (E *e = table[b]; e; e = e->next) n++; printf("bucket %d: %d\n", b, n); }
return 0;
}
✅ Verified output
ravi -> 99 bob found? 0 bucket 0: 0 bucket 1: 0 bucket 2: 2 bucket 3: 2 bucket 4: 1 bucket 5: 0 bucket 6: 0 bucket 7: 1
Graph BFS with an adjacency matrix
Breadth-first search uses a queue to visit nodes in order of distance, so it finds the fewest hops from the start to every node.
#include <stdio.h>
#define V 6
int main(void) {
int adj[V][V] = {0};
int edges[][2] = {{0, 1}, {0, 2}, {1, 3}, {2, 3}, {3, 4}, {4, 5}};
for (int i = 0; i < 6; i++) { adj[edges[i][0]][edges[i][1]] = adj[edges[i][1]][edges[i][0]] = 1; }
int dist[V], q[V], head = 0, tail = 0;
for (int i = 0; i < V; i++) dist[i] = -1;
dist[0] = 0; q[tail++] = 0;
while (head < tail) {
int u = q[head++];
for (int w = 0; w < V; w++)
if (adj[u][w] && dist[w] < 0) { dist[w] = dist[u] + 1; q[tail++] = w; }
}
for (int i = 0; i < V; i++) printf("node %d: %d hops\n", i, dist[i]);
return 0;
}
✅ Verified output
node 0: 0 hops node 1: 1 hops node 2: 1 hops node 3: 2 hops node 4: 3 hops node 5: 4 hops
Dynamic programming: coin change
Build a table of answers for every smaller amount; each entry uses the best earlier one. Here: fewest coins for every amount up to the target.
#include <stdio.h>
#include <limits.h>
int main(void) {
int coins[] = {1, 5, 10, 20}, n = 4, target = 63;
int best[64], used[64];
best[0] = 0;
for (int a = 1; a <= target; a++) {
best[a] = INT_MAX;
for (int c = 0; c < n; c++)
if (coins[c] <= a && best[a - coins[c]] + 1 < best[a]) { best[a] = best[a - coins[c]] + 1; used[a] = coins[c]; }
}
printf("%d rupees needs %d coins:", target, best[target]);
for (int a = target; a > 0; a -= used[a]) printf(" %d", used[a]);
printf("\n");
return 0;
}
✅ Verified output
63 rupees needs 6 coins: 1 1 1 20 20 20
Generic swap with void pointers and memcpy
void * can point to any type. Together with a size, memcpy lets one function work on ints, doubles or structs.
#include <stdio.h>
#include <string.h>
void swap(void *a, void *b, size_t size) {
unsigned char tmp[64];
memcpy(tmp, a, size);
memcpy(a, b, size);
memcpy(b, tmp, size);
}
typedef struct { char name[8]; int age; } P;
int main(void) {
int x = 1, y = 2;
double d1 = 1.5, d2 = -3.25;
P p1 = {"Asha", 15}, p2 = {"Ravi", 16};
swap(&x, &y, sizeof x);
swap(&d1, &d2, sizeof d1);
swap(&p1, &p2, sizeof p1);
printf("%d %d | %.2f %.2f | %s %d\n", x, y, d1, d2, p1.name, p1.age);
return 0;
}
✅ Verified output
2 1 | -3.25 1.50 | Ravi 16
Variadic functions with stdarg
A function can take a variable number of arguments with ..., read them with va_list, and must know how many there are (a count or a format string).
#include <stdio.h>
#include <stdarg.h>
double mean(int count, ...) {
va_list ap;
va_start(ap, count);
double sum = 0;
for (int i = 0; i < count; i++) sum += va_arg(ap, double);
va_end(ap);
return count ? sum / count : 0;
}
void logf_(const char *level, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
printf("[%s] ", level);
vprintf(fmt, ap);
printf("\n");
va_end(ap);
}
int main(void) {
printf("%.2f %.2f\n", mean(3, 90.0, 80.0, 70.0), mean(2, 1.5, 2.5));
logf_("INFO", "%d students, top score %d", 42, 98);
return 0;
}
✅ Verified output
80.00 2.00 [INFO] 42 students, top score 98
Endianness and byte order
Multi-byte numbers are stored lowest byte first (little-endian) on most CPUs. Network protocols use big-endian, so code converts explicitly.
#include <stdio.h>
#include <stdint.h>
uint32_t to_big_endian(uint32_t x) {
return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | ((x >> 8) & 0xFF00) | (x >> 24);
}
int main(void) {
uint32_t v = 0x11223344;
unsigned char *b = (unsigned char *)&v;
printf("in memory: %02X %02X %02X %02X\n", b[0], b[1], b[2], b[3]);
printf("this CPU is %s-endian\n", b[0] == 0x44 ? "little" : "big");
printf("big-endian value: 0x%08X\n", to_big_endian(v));
unsigned char packet[4] = {0x00, 0x00, 0x01, 0xF4};
uint32_t port = (uint32_t)packet[0] << 24 | packet[1] << 16 | packet[2] << 8 | packet[3];
printf("decoded from the wire: %u\n", port);
return 0;
}
✅ Verified output
in memory: 44 33 22 11 this CPU is little-endian big-endian value: 0x44332211 decoded from the wire: 500
Ring buffer for streaming data
A ring buffer keeps the last N samples in a fixed array, overwriting the oldest: the standard way to buffer sensor or UART data on a microcontroller.
#include <stdio.h>
#define N 5
typedef struct { int data[N]; int head, count; } Ring;
void put(Ring *r, int v) { r->data[r->head] = v; r->head = (r->head + 1) % N; if (r->count < N) r->count++; }
double avg(const Ring *r) { long s = 0; for (int i = 0; i < r->count; i++) s += r->data[i]; return r->count ? (double)s / r->count : 0; }
int main(void) {
Ring r = {0};
int readings[] = {20, 22, 21, 35, 23, 24, 22, 21};
for (int i = 0; i < 8; i++) {
put(&r, readings[i]);
printf("sample %d -> moving average %.1f\n", readings[i], avg(&r));
}
return 0;
}
✅ Verified output
sample 20 -> moving average 20.0 sample 22 -> moving average 21.0 sample 21 -> moving average 21.0 sample 35 -> moving average 24.5 sample 23 -> moving average 24.2 sample 24 -> moving average 25.0 sample 22 -> moving average 25.0 sample 21 -> moving average 25.0
🛠️ Set up and verify
- Install
winget install MSYS2.MSYS2 (then in MSYS2: pacman -S mingw-w64-ucrt-x86_64-gcc)
xcode-select --install
sudo apt install build-essential gdb
- Editor: Visual Studio Code with C/C++ (Microsoft), CodeLLDB or the GDB debugger.
- Create
hello.c#include <stdio.h> int main(void) { printf("Hello, GayaGnan!\n"); return 0; } - Run it and check the version
gcc hello.c -o hello && ./hello gcc --version
- Health check: paste what your terminal printed.
Install one language at a time. These commands only install or print versions; nothing here deletes files. Versions change: check the official page and note the version you installed.
🧭 The 14-lab path
- 00 · Setup and verify — install the runtime and editor, run hello world, print the version, fix PATH
- 01 · Syntax and trace — follow each line and the value of every variable
- 02 · Logic — conditionals and decision trees
- 03 · Loops — trace tables and loop challenges
- 04 · Functions — break problems down and test each piece
- 05 · Data structures — lists, maps, stacks, queues and trees you can see
- 06 · Debugging — bug cases with a hint ladder
- 07 · Algorithms — search, sort and Big-O comparison
- 08 · Files and data — read and write fixture files
- 09 · API or database — talk to a mock API or a sandbox database
- 10 · Testing — write tests and read failing assertions
- 11 · Security — secure coding and dependency hygiene for this language
- 12 · Project — one portfolio mini-project
- 13 · Assessment — quiz, coding test, rubric and an explain-your-answer prompt
📚 Modules
- types
- operators
- branches
- loops
- functions
- arrays
- strings
- pointers
- pointer arithmetic
- structs
- dynamic memory
- files
- compile and link
- debugging
- complexity
🏗️ Mini-projects
- Terminal quiz
- Array algorithm visualiser
- Contact book with synthetic records
Projects use synthetic data only.