About this course
Basic tier: pointer arithmetic, buffer safety and struct layout. Advanced tier: the heap, hash tables and binary records - eight modules culminating in the MiniBank transaction engine capstone.
This is a 6-week short course - enough depth to build real projects and a portfolio piece, without a long commitment. It runs online in the August 2026 cohort (starting 1 August 2026) and is taught the EchoLens way: you learn by doing real, gradeable work rather than just watching lectures.
What's included
- Live, instructor-led online sessions across 8 weeks (20 hours total).
- Hands-on coding quests you solve inside the EchoLens browser compiler - nothing to install.
- Gems, stages and a leaderboard that keep you moving instead of grade anxiety.
- A verified certificate with a scannable QR code, ready to share on LinkedIn, when you finish.
- Completely free - no fee, just create an account and start.
What you will learn
Course outline - level by level
8 leveles, each with hands-on quests you clear in the portal.
- Level 1. Basic 1: Procedural Abstraction and the Call Stack - Calling a function pushes an activation record onto the stack containing the return address, the saved base pointer and the local variables. That record explains three things at once: why C passes arguments by value, why returning the address of a local variable is a defect, and why deep recursion eventually exhausts the stack. Key rules: - Arguments are copied. To let a function modify a caller variable, pass its address. - Never return a pointer to a local variable - that memory is reclaimed the moment the function returns. - Recursion depth costs stack space per frame; tail-shaped recursion may or may not be optimised, so do not rely on it. - Declare in the header, define in the source. Anything not in the header should be marked static. Worked example - recursive GCD and fast exponentiation: long gcd(long a, long b) { return b == 0 ? a : gcd(b, a % b); } long power(long base, long exp) { if (exp == 0) return 1; long half = power(base, exp / 2); return (exp % 2) ? half * half * base : half * half; }
- Level 2. Basic 2: Contiguous Layouts and Two Dimensional Arrays - An array name in an expression decays to a pointer to its first element, which is why indexing and pointer arithmetic are the same operation written two ways. A two dimensional array is stored in row major order as one contiguous block, which is why iterating rows then columns is dramatically faster than the reverse - the fast order walks memory in the direction the cache prefetches. Key rules: - arr[i] is defined as the value at (arr + i). They are interchangeable. - Pointer arithmetic scales by the element size - adding one moves one element, not one byte. - Row major layout: element (r, c) of an array with C columns sits at offset (r*C + c). - Traverse in memory order. Row then column is cache friendly, column then row is not. Worked example - in-place transpose walking memory in row major order: void transpose(int m[][4], int n) { for (int r = 0; r < n; r++) for (int c = r + 1; c < n; c++) { int t = m[r][c]; m[r][c] = m[c][r]; m[c][r] = t; } }
- Level 3. Basic 3: Strings, Buffers and Memory Safety - A C string is a character array with a terminating zero byte, and every library function trusts you to have put that byte there. The entire family of buffer overflow vulnerabilities comes from functions that write until they find a terminator with no knowledge of how much room they have. The professional habit: use the bounded variants, always reserve one byte for the terminator, and treat any function that cannot be told a size limit as unusable in production. Key rules: - A buffer for n visible characters needs n+1 bytes. The terminator is not optional. - Use snprintf rather than sprintf, and prefer bounded copies over unbounded ones. - Never use gets - it cannot be used safely under any circumstance and has been removed from the standard. - strlen counts characters up to the terminator; it is not the allocation size. Worked example - a bounded copy that always terminates: void safe_copy(char *dst, size_t dst_size, const char *src) { if (dst_size == 0) return; size_t i = 0; while (i + 1 < dst_size && src[i]) { dst[i] = src[i]; i++; } dst[i] = '\0'; }
- Level 4. Basic 4: Structs, Padding, Unions and Binary Layout - A struct is not the sum of its members. The compiler inserts padding so that each member begins at an address that is a multiple of its own alignment requirement, and adds trailing padding so arrays of the struct stay aligned. Reordering members from largest to smallest often shrinks a struct by a third with no code change. Unions place all members at the same address and are the standard tool for tagged variant records. Key rules: - A member of size s is placed at the next offset divisible by s; total size rounds up to the largest member alignment. - Ordering members from largest to smallest usually minimises padding. - A union is exactly as large as its largest member - only one member is valid at a time, so pair it with a tag. - Never write a struct straight to disk or a socket without a defined layout; padding is not portable. Worked example - two identical field sets, different sizes: struct wasteful { char a; int b; char c; }; /* likely 12 bytes */ struct packed { int b; char a; char c; }; /* likely 8 bytes */
- Level 5. Advanced 1: The Heap, Allocation and Leak Discipline - The heap is memory whose lifetime you control rather than the compiler. That control is the source of the four defects that dominate C bug reports: the leak, the use after free, the double free and the buffer overrun on heap memory. Every one is preventable by a discipline: every allocation has exactly one owner, and the free lives in the same file as the allocation. Key rules: - Every allocation call has exactly one matching release call on every path, including error paths. - After releasing a pointer, set it to null - a null dereference crashes loudly, a dangling one corrupts silently. - realloc may move the block - always assign its result, never assign it over the only pointer you have. - Zeroing allocation costs a pass over the memory - use it when the zero state matters, not by reflex. Worked example - a growable array that survives reallocation failure: int push(int **arr, size_t *len, size_t *cap, int value) { if (*len == *cap) { size_t next = *cap ? *cap * 2 : 8; int *tmp = realloc(*arr, next * sizeof(int)); if (!tmp) return 0; *arr = tmp; *cap = next; } (*arr)[(*len)++] = value; return 1; }
- Level 6. Advanced 2: Linked Structures, Stacks, Queues and Hash Tables - Once memory can be requested at run time, data structures stop being fixed arrays and become graphs of nodes. A linked list gives constant time insertion at the cost of cache locality. A hash table with separate chaining is a fixed array of list heads, and its performance collapses from constant to linear when the hash distributes badly - measuring chain length matters more than choosing a clever hash. Key rules: - Load factor equals stored entries divided by bucket count - above roughly 0.75, grow the table and rehash. - Average lookup cost in a chained table is one plus half the load factor. - A stack is last in first out and a queue is first in first out - the choice encodes the algorithm. - Every node structure needs a matching destroy function that walks and releases the whole structure. Worked example - separate chaining insert with a simple string hash: unsigned long hash(const char *s) { unsigned long h = 5381; while (*s) h = h * 33 + (unsigned char)*s++; return h; } void insert(struct node **buckets, size_t n, const char *key, int value) { size_t i = hash(key) % n; struct node *node = make_node(key, value); node->next = buckets[i]; buckets[i] = node; }
- Level 7. Advanced 3: File Streams, Binary Records and Durable Writes - Text mode is for humans and binary mode is for machines, and mixing them is where most file corruption starts. Binary records give constant time access to record number n because the offset is simply n multiplied by the record size. Durability is the harder half: a system that must survive a crash writes to a temporary file, flushes it, and only then replaces the original. Key rules: - Record n begins at byte offset n multiplied by the record size. - Open binary files in binary mode explicitly. - A successful write is not a durable write - flush the stream, then rename the temporary file over the original. - Always check the return value of every read and write call. Worked example - atomic replace: write to a temporary file, then rename: int save_atomic(const char *path, const void *data, size_t n) { char tmp[256]; snprintf(tmp, sizeof tmp, "%s.tmp", path); FILE *f = fopen(tmp, "wb"); if (!f) return 0; if (fwrite(data, 1, n, f) != n) { fclose(f); return 0; } fflush(f); fclose(f); return rename(tmp, path) == 0; }
- Level 8. Advanced 4: Systems Integration and the Course Capstone - Integration is a distinct skill from implementation. A program that combines dynamic structures, file persistence and user input has failure modes none of the parts have alone: a partially applied transaction, an index that disagrees with the file, memory freed by one subsystem while another still holds a pointer. The professional answer is a layered design with one owning module per resource and a single entry point for every state change. Key rules: - One module owns each resource - other modules borrow through functions, never raw pointers. - Every state change goes through a single function so logging, validation and rollback live in one place. - A transaction is applied only after every precondition is checked. - A regression harness that replays a recorded input file catches more than manual testing. Worked example - a single guarded entry point for state change: int apply_transfer(Bank *b, int from, int to, long paisa) { if (paisa <= 0) return ERR_AMOUNT; Account *a = find(b, from), *z = find(b, to); if (!a || !z) return ERR_NO_ACCOUNT; if (a->balance < paisa) return ERR_FUNDS; a->balance -= paisa; z->balance += paisa; return journal_append(b, from, to, paisa); }
How you submit: Coding quests solved in the built-in EchoLens compiler.
Who it's for
Advanced C Programming suits learners at a beginner to intermediate level who want a practical, project-based route into Advanced C Programming. You need only a browser and an internet connection - all coding runs inside the EchoLens compiler, so there is nothing to set up.
Certificate
Finish every stage and EchoLens issues a verified certificate carrying a QR code anyone can scan to confirm it on our site. You can add it to your CV or share it to LinkedIn in one click.