FreeShort Course

Advanced C Programming

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.

FreeNo fee
8 weeks20 hours
Short CourseBeginner to Intermediate
OnlineLive & instructor-led

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

Call stack & activation recordsPass-by-value vs pass-by-addressPointer arithmeticRow-major memory layoutBuffer safety & snprintfStruct padding & alignmentUnionsmalloc/realloc/free disciplineLinked lists, stacks & queuesHash tables with chainingBinary file streamsAtomic writesSystems integration

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.

More Short Courses

Python for Data ScienceRs 12,500 · 6 weeksGenerative AI EssentialsRs 14,000 · 6 weeksData Analytics with SQL & Power BIRs 13,500 · 6 weeksIntroduction to Machine LearningRs 13,000 · 6 weeks