
Introduction
Good software starts with solid fundamentals. This guide is a lecture sheet for CS students, newbie web devs, backend/frontend engineers, and anyone picking up programming. We’ll cover core programming concepts, then dive deep into C—a language that teaches you memory, pointers, and how computers actually work.
Whether you’re catching up on classes or learning from scratch, read this top-to-bottom. Each section builds on the last.
Part 1: Core Programming Fundamentals
graph LR Problem[Problem] --> Abstraction[Abstraction] Abstraction --> DataStructure[Data Structure] DataStructure --> Algorithm[Algorithm] Algorithm --> Code[Code] Code --> Test[Test & Debug] Test --> Solution[Solution]
1.1 What is Programming?
Programming is teaching a computer to solve a problem by breaking it into steps and expressing those steps as code. At the core:
- Input: data you give the program
- Processing: logic to transform input
- Output: results you get back
Example: write a program that takes a list of numbers and returns the sum.
1.2 Abstraction & Decomposition
Abstraction means hiding complexity and focusing on what matters.
- Break a large problem into smaller, manageable chunks (functions, modules).
- Each piece should have a clear responsibility and a simple interface.
- Hide implementation details; users of your code only need to know what it does, not how.
Example:
// Instead of writing the same sum logic 10 times, abstract it into a function:
int sum_array(int arr[], int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += arr[i];
}
return total;
}
// Now use it anywhere:
int result = sum_array(my_numbers, 5);
1.3 Data Structures: storing and organizing data
Different structures suit different jobs:
- Arrays: fixed-size collections, indexed access O(1).
- Linked Lists: dynamic size, traversal O(n), insertion O(1) if position known.
- Stacks: LIFO (Last In First Out), useful for undo/redo.
- Queues: FIFO (First In First Out), useful for scheduling.
- Hash Tables: key-value lookup O(1) average.
- Trees: hierarchical, binary search trees for range queries.
- Graphs: relations and networks.
Choose based on your access patterns: if you need fast lookup by key, use a hash table; if you need sorted order, use a tree.
1.4 Algorithms: steps to solve the problem
An algorithm is a sequence of steps to achieve a goal.
- Sorting: quicksort, mergesort, heapsort.
- Searching: linear search, binary search.
- Graph traversal: BFS (breadth-first), DFS (depth-first).
- Dynamic programming: break problem into overlapping subproblems.
1.5 Complexity Analysis (Big-O)
How does runtime or memory grow with input size?
- O(1): constant time (array access by index).
- O(log n): logarithmic (binary search).
- O(n): linear (iterate once).
- O(n²): quadratic (nested loops).
- O(2^n): exponential (avoid if possible).
Tip: understand complexity early; a slow algorithm ruins performance at scale.
Part 2: Getting Started with C
C is a small, powerful language that teaches you how programs really work. You’ll learn memory, pointers, and hardware close to the metal. This knowledge transfers to any language.
2.1 Your First C Program
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
#include <stdio.h>: include the standard I/O library.int main(): entry point; returns an integer status (0 = success).printf(): print formatted text.return 0;: tell OS the program succeeded.
Compile and run:
gcc hello.c -o hello
./hello
2.2 C Keywords (the language building blocks)
C has only ~32 keywords:
- Data types:
int,float,char,double,void. - Flow control:
if,else,for,while,do,switch,case,break,continue. - Functions:
return,void. - Storage:
static,extern,auto,register. - Type qualifiers:
const,volatile. - Structures:
struct,union,enum. - Pointers:
*(dereference),&(address-of).
You don’t need to memorize all; we’ll cover them as we go.
2.3 Variables & Basic Data Types
A variable is a named location in memory holding a value.
Declaration: int age = 25;
- Type:
int(integer) - Name:
age - Value:
25
Common types:
| Type | Size | Range | Example |
|---|---|---|---|
char | 1 byte | -128 to 127 | 'A', '1' |
int | 4 bytes | -2^31 to 2^31-1 | 42, -10 |
float | 4 bytes | decimal | 3.14 |
double | 8 bytes | precise decimal | 3.14159265359 |
long | 4-8 bytes | larger integers | 1000000L |
2.4 Input/Output (I/O)
Read and write data.
Output:
#include <stdio.h>
int main() {
int age = 25;
float height = 5.9;
printf("Age: %d\n", age);
printf("Height: %.1f feet\n", height);
return 0;
}
Format specifiers:
%d: integer%f: float%s: string%c: character
Input:
int age;
printf("Enter your age: ");
scanf("%d", &age); // & = address of age
2.5 Control Flow (if, loops)
Conditional:
int score = 75;
if (score >= 90) {
printf("A\n");
} else if (score >= 80) {
printf("B\n");
} else {
printf("C\n");
}
Loops:
// for loop (iterate n times)
for (int i = 0; i < 5; i++) {
printf("%d\n", i);
}
// while loop (repeat while condition true)
int count = 0;
while (count < 5) {
printf("%d\n", count);
count++;
}
// do-while (runs at least once)
int x = 0;
do {
printf("This runs at least once\n");
x++;
} while (x < 5);
Part 3: Functions
Functions are reusable blocks of code.
// Define a function
int add(int a, int b) {
return a + b;
}
// Call it
int result = add(3, 5); // result = 8
Parts:
- Return type:
int(what it returns). - Name:
add. - Parameters:
int a, int b. - Body: code between
{ }.
Why use functions?
- Reusability
- Readability
- Testability
Part 4: Memory & Pointers
This is critical. Pointers are why C is powerful and tricky.
4.1 Memory layout
Every variable lives at an address in memory.
int age = 25;
// & = "address of"
printf("Value: %d\n", age); // 25
printf("Address: %p\n", &age); // 0x7ffc8b2b4a4c (example)
4.2 Pointers
A pointer is a variable that holds an address.
int age = 25;
int *p = &age; // p points to age
printf("p holds address: %p\n", p); // same as &age
printf("value at p: %d\n", *p); // 25 (* = dereference)
Why pointers?
- Dynamic memory allocation
- Passing variables by reference (modify original, not copy)
- Building complex data structures (linked lists, trees)
4.3 Dynamic memory
Stack (automatic cleanup) vs heap (you manage).
#include <stdlib.h>
// Allocate
int *arr = (int *)malloc(5 * sizeof(int));
arr[0] = 10;
arr[1] = 20;
// Free (critical!)
free(arr);
arr = NULL;
Important: always free() what you malloc().
Part 5: Arrays & Strings
5.1 Arrays
Fixed-size collection of same type.
int scores[5] = {90, 85, 88, 92, 78};
// Access
printf("%d\n", scores[0]); // 90
// Iterate
for (int i = 0; i < 5; i++) {
printf("%d\n", scores[i]);
}
5.2 Strings
In C, strings are char arrays ending with \0.
char name[20] = "Alice";
// Actually: A-l-i-c-e-\0 (null terminator)
// Read string
scanf("%s", name);
// Print string
printf("Name: %s\n", name);
Use <string.h> for operations:
#include <string.h>
strlen(name); // length
strcpy(dest, src); // copy
strcat(a, b); // concatenate
strcmp(a, b); // compare
Part 6: Structures (organizing related data)
Group multiple data types together.
struct Person {
char name[50];
int age;
float height;
};
// Create instance
struct Person p1;
strcpy(p1.name, "Alice");
p1.age = 25;
// Or use pointer
struct Person *p2 = (struct Person *)malloc(sizeof(struct Person));
strcpy(p2->name, "Bob"); // -> accesses through pointer
p2->age = 30;
free(p2);
Part 7: Standard Library (STL) vs Manual Approaches
C has a standard library with useful functions.
7.1 Common libraries
<stdio.h>: input/output<stdlib.h>: memory allocation, random numbers<string.h>: string operations<math.h>: math functions (sin, cos, sqrt, etc.)<time.h>: time and date<assert.h>: assertions (for debugging)
7.2 STL vs Manual
Using STL (safe, tested):
#include <string.h>
char name[50];
strcpy(name, "Alice"); // Copy string safely
Manual (error-prone, avoid):
// Don't do this without bounds checking:
char name[10];
strcpy(name, "This is a very long string that overflows"); // CRASH!
Lesson: use library functions when available; they’re tested and safe.
Part 8: Common Data Structure Implementations
8.1 Linked List
struct Node {
int data;
struct Node *next;
};
// Create a list
struct Node *head = (struct Node *)malloc(sizeof(struct Node));
head->data = 10;
head->next = NULL;
// Add to front
struct Node *new_node = (struct Node *)malloc(sizeof(struct Node));
new_node->data = 5;
new_node->next = head;
head = new_node;
8.2 Stack (simple array-based)
#define MAX 100
int stack[MAX];
int top = -1; // empty
void push(int x) {
if (top < MAX - 1) {
stack[++top] = x;
}
}
int pop() {
if (top >= 0) {
return stack[top--];
}
return -1; // error
}
Part 9: Algorithms with Complexity Examples
9.1 Sorting
Bubble Sort (O(n²), simple but slow):
void bubble_sort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// swap
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
Binary Search (O(log n), requires sorted array):
int binary_search(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // not found
}
Part 10: Debugging & Testing
10.1 Debugging techniques
#include <assert.h>
int divide(int a, int b) {
assert(b != 0); // abort if b is 0
return a / b;
}
Print debugging:
int result = some_function();
printf("DEBUG: result = %d\n", result);
Use a debugger (GDB):
gcc -g myprogram.c -o myprogram
gdb ./myprogram
(gdb) break main
(gdb) run
(gdb) print variable_name
(gdb) step
10.2 Testing
Write small tests to verify functions:
void test_add() {
assert(add(2, 3) == 5);
assert(add(0, 0) == 0);
assert(add(-1, 1) == 0);
printf("add() tests passed\n");
}
Part 11: Common Pitfalls & Best Practices
| Pitfall | Why bad | Fix |
|---|---|---|
| Forgetting to free memory | Memory leak | Call free() for every malloc() |
| Buffer overflow | Crash/security | Use bounds checking or safer functions |
| Uninitialized variables | Undefined behavior | Always initialize: int x = 0; |
| Not checking malloc return | Crash | if (ptr == NULL) { ... } |
| Pointer dereference errors | Segmentation fault | Verify pointer is valid before *ptr |
Part 12: From C to Other Languages
Once you master C, other languages feel familiar:
- C++: adds objects and STL containers (vectors, maps, etc.).
- Java/C#: memory is auto-managed (garbage collected).
- Python/JavaScript: same concepts, simpler syntax.
Fundamentals apply everywhere.
Next Steps & Resources
- Write small C programs (loop exercises, arrays, functions).
- Implement a linked list and a stack from scratch.
- Solve 20 algorithmic problems (LeetCode, Project Euler).
- Read Kernighan & Ritchie’s “The C Programming Language.”
- Build a small project (calculator, task manager, file parser).
Conclusion
Fundamentals are everything. Spend time understanding memory, pointers, data structures, and complexity. These skills transfer across languages and decades of programming.
Good luck, and happy coding! 🚀