๐
What is a Prime Number? โ Plain English First
Build the idea from scratch before writing a single line of code
Concept
Plain English Definition
A prime number is a whole number greater than 1 that can only be divided evenly by exactly two numbers: 1 and itself. That's it. Nothing else divides it without leaving a remainder.
7 is prime โ try dividing 7 by 2, 3, 4, 5, 6. Every time there is a remainder. Only 1 and 7 divide it cleanly.
6 is NOT prime โ 6 รท 2 = 3 exactly. A third number (2) divides it. So 6 has more than two divisors โ not prime.
1 is NOT prime โ special case. 1 has only ONE divisor (itself). Prime requires exactly two. So 1 is excluded by definition.
7 is prime โ try dividing 7 by 2, 3, 4, 5, 6. Every time there is a remainder. Only 1 and 7 divide it cleanly.
6 is NOT prime โ 6 รท 2 = 3 exactly. A third number (2) divides it. So 6 has more than two divisors โ not prime.
1 is NOT prime โ special case. 1 has only ONE divisor (itself). Prime requires exactly two. So 1 is excluded by definition.
Primes highlighted โ numbers 1 to 50
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Prime
Not prime
Special (1)
The key question C asks for every number n: "Does any number between 2 and n-1 divide n with no remainder?" If yes โ not prime. If no โ prime. In code, remainder is the
Smart shortcut โ only check up to โn: If n has a divisor larger than โn, it must also have one smaller than โn. So once we have checked all numbers up to โn without finding a divisor, we are done โ n is prime. For n=100, โ100=10, so we only need to check 2 through 10 โ not 2 through 99.
% operator. n % i == 0 means i divides n exactly.
Smart shortcut โ only check up to โn: If n has a divisor larger than โn, it must also have one smaller than โn. So once we have checked all numbers up to โn without finding a divisor, we are done โ n is prime. For n=100, โ100=10, so we only need to check 2 through 10 โ not 2 through 99.
| Number | Divisors checked | Result | Reason |
|---|---|---|---|
| 2 | none (loop doesn't start) | PRIME | 2 is the smallest prime โ no divisors between 2 and 1 |
| 9 | 2, 3 | NOT PRIME | 9 % 3 == 0 โ divisible by 3 |
| 17 | 2, 3, 4 (โ17 โ 4.1) | PRIME | None of 2,3,4 divide 17 โ so prime |
| 100 | 2 (โ100=10) | NOT PRIME | 100 % 2 == 0 โ stops immediately |
| 97 | 2,3,4,5,6,7,8,9 (โ97โ9.8) | PRIME | None divide 97 โ last prime before 100 |
The โn trick saves huge time. For n = 1,000,000: naive check needs 1,000,000 divisions. With โn, only 1,000 divisions. That is 1000ร faster โ same answer, less work.
flowchart โ step by step logic
๐
Flowchart โ isPrime Logic Visualised
Every decision, every loop, every exit path drawn as a proper flowchart
Flowchart
The flowchart has two nested decisions. The outer loop walks every number from 2 to n. For each number, the inner function checks divisibility from 2 up to โnum. Two exit paths exist from the inner check: finding a divisor (not prime) or exhausting all checks without finding one (prime). The connector circle A routes prime numbers to the print step without re-entering the inner check.
Complete isPrime flowchart โ outer loop + inner divisibility check
- 1Start num at 2 โ 1 is excluded (special case). Loop from 2 to n.
- 2Set isPrime = 1 โ assume prime until proven otherwise.
- 3Inner loop: i = 2, while iรi โค num โ check divisors only up to โnum.
- 4If num % i == 0 โ set
isPrime = 0and break โ a divisor was found. - 5After inner loop: if
isPrimeis still 1 โ print num. It survived all checks. - 6num++ โ move to next number, loop back to outer condition.
way 1 โ function isPrime()
โ๏ธ
Way 1 โ isPrime() Function โ Clean, Reusable, Readable
Separate function returns 1 (prime) or 0 (not) โ main just calls it in a loop
Function
The cleanest approach: pull the prime-checking logic into a separate function
isPrime(int n) that returns 1 if n is prime and 0 if not. The main function simply loops from 2 to 100 and calls isPrime for each number. This follows the single responsibility principle โ one function does one thing. The function is also reusable: you can call it from anywhere, not just from main.
- 1Handle edge cases first:
if(n < 2) return 0โ 0 and 1 are not prime. - 2Inner loop:
for(i=2; i*i<=n; i++)โ only check up to โn. Efficient. - 3If divisor found:
return 0immediately โ no need to continue checking. - 4If loop completes:
return 1โ no divisor found โ prime. - 5Main loop: call
isPrime(num)for each num from 2 to 100. Print those returning 1.
#include <stdio.h> /* Returns 1 if n is prime, 0 otherwise */ int isPrime(int n) { if (n < 2) return 0; /* 0 and 1 are NOT prime */ if (n == 2) return 1; /* 2 is the only even prime */ if (n % 2 == 0) return 0; /* all other even โ not prime */ /* Check odd divisors from 3 up to โn */ for (int i = 3; i * i <= n; i += 2) { if (n % i == 0) return 0; /* divisor found โ not prime */ } return 1; /* survived all checks โ prime */ } int main() { int n = 100; int count = 0; printf("Prime numbers from 1 to %d:\n", n); printf("--------------------------------\n"); for (int num = 2; num <= n; num++) { if (isPrime(num)) { /* call function โ clean! */ printf("%4d", num); count++; if (count % 10 == 0) printf("\n"); /* 10 per line */ } } printf("\n--------------------------------\n"); printf("Total primes found: %d\n", count); return 0; }
Prime numbers from 1 to 100: -------------------------------- 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 -------------------------------- Total primes found: 25
Three quick optimisations inside isPrime: (1) Return 0 immediately for n < 2. (2) Return 1 immediately for n == 2 (only even prime). (3) Return 0 for all other even numbers โ halves the remaining checks. Then step by 2 (
i += 2) โ only check odd divisors. Together these make the function roughly 4ร faster than the naive version.way 2 โ pointer version
๐ฏ
Way 2 โ Pointer Version โ Pass by Pointer, Walk by Pointer
checkPrime(int *n, int *result) โ function writes answer through pointer โ pointer walk prints
Pointers
The pointer version demonstrates two pointer patterns at once. Pattern 1 โ output pointer:
checkPrime(int *n, int *result) receives the number to test via pointer and writes the answer (1 or 0) into *result โ communicating the result back to the caller without a return value. Pattern 2 โ pointer walk: to print primes in an array, we use a pointer that walks the array instead of an index. Both patterns appear in real C codebases constantly.
- 1Parameter
int *nโ receive number by pointer. Access with*n. Read-only โ we don't need to modify it, but it demonstrates pointer passing. - 2Parameter
int *resultโ write the answer here:*result = 1if prime,*result = 0if not. Caller reads fromresultafter the call. - 3Pointer walk print:
int *p = primes; while(p < primes+count) printf("%d ", *p++);โ walks the prime storage array. - 4Pointer arithmetic:
primes+countis the one-past-last sentinel.p < endis the loop condition.
#include <stdio.h> #include <math.h> /* Pointer version โ writes result through *result pointer */ void checkPrime(const int *n, int *result) { int num = *n; /* read value through pointer */ *result = 1; /* assume prime via pointer */ if (num < 2) { *result = 0; return; } if (num == 2) { *result = 1; return; } if (num % 2 == 0) { *result = 0; return; } for (int i = 3; i * i <= num; i += 2) { if (num % i == 0) { *result = 0; /* write NOT PRIME through ptr */ return; } } /* *result stays 1 โ prime */ } /* Print array via pointer walk โ no index used */ void printViaPtrWalk(const int *arr, int count) { const int *p = arr; /* pointer starts at arr[0] */ const int *end = arr + count; /* one-past-last sentinel */ int col = 0; while (p < end) { printf("%4d", *p); /* dereference pointer */ p++; /* advance to next element */ if (++col % 10 == 0) printf("\n"); } } int main() { int n = 100; int storage[30]; /* there are 25 primes โค 100 */ int count = 0; int result = 0; for (int num = 2; num <= n; num++) { checkPrime(&num, &result); /* pass ADDRESSES โ pointer API */ if (result) storage[count++] = num; /* save prime in array */ } printf("Primes 1-%d via pointer walk:\n", n); printf("--------------------------------\n"); printViaPtrWalk(storage, count); printf("\n--------------------------------\n"); printf("Count : %d\n", count); printf("Largest: %d\n", storage[count-1]); printf("Smallest: %d\n", storage[0]); /* Pointer arithmetic โ sum of primes */ int sum = 0; const int *p = storage; const int *e = storage + count; while(p < e) sum += *p++; printf("Sum : %d\n", sum); return 0; }
Primes 1-100 via pointer walk: -------------------------------- 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 -------------------------------- Count : 25 Largest: 97 Smallest: 2 Sum : 1060
Why pass
&num as const int *n? The function does not need to modify the number โ so const documents that promise. But passing by pointer rather than value demonstrates the output-pointer pattern: *result is how the function communicates its answer back. This is exactly how scanf works โ you pass &variable and it writes the answer through the pointer.way 3 โ array (sieve idea)
๐ฆ
Way 3 โ Array Version โ Sieve of Eratosthenes
Boolean array[101] marks composites โ remaining unmarked = primes โ O(n log log n)
Array + Sieve
The Sieve of Eratosthenes is the smartest approach โ it uses an array of booleans. Start by marking every number as prime (1). Then for each prime p starting at 2, mark all its multiples as not prime (0). When done, every index still marked 1 is a prime. The name comes from ancient Greece: imagine crossing out multiples on a number grid like a sieve โ primes are what remains.
Key insight: for each prime
Key insight: for each prime
p, start crossing out from pรp โ everything before that was already crossed out by smaller primes. This gives O(n log log n) time โ much faster than checking each number individually.
Sieve idea visualised โ mark[i] = 0 means i is NOT prime
- 1Create boolean array:
int mark[N+1]. Set all to 1 (assume prime). Setmark[0] = mark[1] = 0(not prime). - 2Outer loop p = 2 to โN: if
mark[p] == 1(p is still prime), mark all its multiples. - 3Inner loop starts at pรp:
for(j = p*p; j <= N; j += p)โ setmark[j] = 0. - 4Collect primes via pointer walk: walk from
&mark[2]to&mark[N]. Where*p == 1, the index is prime. - 5Count pointer arithmetic: index from pointer:
p - markgives the current number being checked.
#include <stdio.h> #include <string.h> #define N 100 /* Build the sieve โ marks composite numbers as 0 */ void buildSieve(int *mark, int n) { /* Step 1 โ fill array with 1 (assume all prime) */ for(int i = 0; i <= n; i++) mark[i] = 1; mark[0] = mark[1] = 0; /* 0 and 1 not prime */ /* Step 2 โ sieve: for each prime p, cross its multiples */ for (int p = 2; p * p <= n; p++) { if (mark[p] == 1) { /* p is still prime */ /* cross out p*p, p*p+p, p*p+2p ... */ for (int j = p * p; j <= n; j += p) mark[j] = 0; /* j is a multiple of p */ } } } /* Print primes using pointer walk โ index via ptr arithmetic */ int printPrimesPtr(const int *mark, int n) { const int *p = mark + 2; /* start from index 2 */ const int *end = mark + n + 1;/* one past mark[n] */ int count = 0, col = 0; printf("Primes via sieve (1 to %d):\n", n); printf("-------------------------------\n"); while (p < end) { if (*p == 1) { /* marked prime */ printf("%4d", (int)(p - mark)); /* index = number */ count++; col++; if (col % 10 == 0) printf("\n"); } p++; /* advance pointer */ } return count; } /* Stats via pointer scan */ void sieveStats(const int *mark, int n) { const int *p = mark + 2; const int *e = mark + n + 1; int sum=0, first=-1, last=-1, cnt=0; while(p < e){ if(*p){ int num = (int)(p - mark); if(first == -1) first = num; last = num; sum += num; cnt++; } p++; } printf("\n-------------------------------\n"); printf("Count : %d\n", cnt); printf("Smallest: %d\n", first); printf("Largest : %d\n", last); printf("Sum : %d\n", sum); printf("Average : %.2f\n", (float)sum/cnt); } int main() { int mark[N + 1]; buildSieve(mark, N); /* pass array as pointer */ printPrimesPtr(mark, N); /* pointer walk + ptr arithmetic */ sieveStats(mark, N); return 0; }
Primes via sieve (1 to 100): ------------------------------- 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 ------------------------------- Count : 25 Smallest: 2 Largest : 97 Sum : 1060 Average : 42.40
Pointer arithmetic for index:
(int)(p - mark) โ p is a pointer into the mark array and mark is a pointer to element 0. Subtracting them gives the offset โ which is exactly the number being checked. No separate index variable needed โ the pointer position is the index.| Approach | Time | Space | Concept taught | Best for |
|---|---|---|---|---|
| Way 1 โ Function | O(nโn) | O(1) | Function, return value, early return | Checking one number or small n |
| Way 2 โ Pointer | O(nโn) | O(n) | Output pointer, pointer walk, ptr arithmetic | Teaching pointer patterns |
| Way 3 โ Sieve | O(n log log n) | O(n) | Boolean array, sieve algorithm, ptr index | All primes up to large n โ fastest |
checklist โ tick when understood
- Definition: Prime = whole number > 1 divisible only by 1 and itself. 1 is NOT prime (one divisor, not two). 2 is the only even prime. Check with
n % i == 0. - โn shortcut: Only check divisors up to โn. Use
i*i <= nto avoidsqrt(). For n=100 checks only up to 10. Roughly โn times faster than checking up to n. - Flowchart logic: Outer loop num=2 to N. Inner loop i=2 while iรiโคnum. If num%i==0 โ isPrime=0, break. After inner loop: if isPrime still 1 โ print. num++, repeat.
- Way 1 โ Function:
isPrime(int n)returns 1 or 0. Handle edge cases first. Check n==2 (only even prime). Loop odd divisorsi+=2.return 0on first divisor found,return 1after loop. - Way 2 โ Pointer:
checkPrime(const int *n, int *result)reads via*n, writes answer via*result. Caller passes&numand&result. Pointer walk print:while(p < end) printf(*p++); - Way 3 โ Sieve:
int mark[N+1]all 1s. mark[0]=mark[1]=0. For each prime p: cross multiples from pรp. Pointer walk:p - markgives index = number. Fastest for finding all primes to N.