C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
42: Practice Exercise: Palindrome and Anagram Checkers
By now, you're comfortable with strings and arrays, but the real gap between a beginner and a professional software engineer is how they approach efficiency. When you're asked to check if a word is a palindrome or if two words are anagrams, the first solution that pops into your head is usually the one that wastes the most resources. I've seen plenty of juniors write code that works on a small test case but chokes in production because they treated C like a high-level language with an infinite memory heap.
The Cost of Copying vs. Walking Inward
If I asked you to check if "racecar" is a palindrome, your first instinct might be to create a second string, reverse the original into it, and then use strcmp() to see if they match. It's intuitive, sure, but it's wasteful. You're allocating extra memory—which in C means dealing with malloc or a fixed-size buffer that might overflow—and you're traversing the string twice.
The better way is to use two pointers. You place one at the start and one at the end, then move them toward the center, comparing characters as you go. If they ever disagree, you're done; it's not a palindrome. You don't need a second buffer, and you only ever look at each character once. In a professional codebase, we avoid unnecessary allocations whenever possible. Why ask the OS for more memory when you can just move two integers?
// The "Walking Inward" approach
int is_palindrome(const char *s) {
int left = 0;
int right = strlen(s) - 1;
while (left < right) {
if (s[left] != s[right]) return 0;
left++;
right--;
}
return 1;
}
Avoiding the Double Loop with a Frequency Table
Anagrams—like "listen" and "silent"—are a bit trickier. A common naive approach is to take the first string and, for every character, loop through the second string to find a match, marking that character as "used" so it isn't counted twice. This results in an O(n²) time complexity. For a ten-letter word, it's fine. For a thousand-word paragraph, your CPU is going to feel it.
Instead of searching, think about counting. Since we're dealing with standard ASCII, we know there are only 256 possible characters. We can create an integer array of size 256 to act as a frequency table. You iterate through the first string and increment the count for each character, then iterate through the second and decrement. If the strings are anagrams, every single slot in that array will be exactly zero at the end.
You're trading a tiny, fixed amount of memory (about 1KB for the array) to move from quadratic time to linear time. That is a trade-off I will take every single time. It's the difference between a program that feels instant and one that hangs as the input size grows.
// The Frequency Table approach
int are_anagrams(const char *s1, const char *s2) {
if (strlen(s1) != strlen(s2)) return 0;
int counts[256] = {0};
for (int i = 0; s1[i] != '\0'; i++) {
counts[(unsigned char)s1[i]]++;
counts[(unsigned char)s2[i]]--;
}
for (int i = 0; i < 256; i++) {
if (counts[i] != 0) return 0;
}
return 1;
}📋 Practical Task
Build a Case-Insensitive Palindrome and Anagram Suite
Write a C program that implements both the palindrome and anagram checkers discussed in this lesson, but with a professional twist: they must be case-insensitive and ignore non-alphanumeric characters. For example, "A man, a plan, a canal: Panama" should be recognized as a palindrome, and "Astronomer" should be an anagram of "Moon starer".
- Implement
int is_palindrome(const char *s)using the two-pointer technique. - Implement
int are_anagrams(const char *s1, const char *s2)using a frequency table. - Use
tolower()from<ctype.h>andisalnum()to handle the normalization. - Create a
mainfunction that tests these functions with at least three "tricky" cases (mixing cases and punctuation) and prints the results clearly to the console.
There are no comments for now.