Checking Palindromes in C#: Multiple Approaches and Techniques

6 minute read

A palindrome is a word, phrase, or sequence that reads the same backward as forward. Classic examples include “level,” “radar,” “civic,” and “madam.” In this comprehensive guide, we’ll explore various methods to detect palindromes in C# with different approaches and optimizations.

What Makes a Word a Palindrome?

A palindrome has these characteristics:

  • Reads the same forwards and backwards
  • Case-insensitive comparison is typically used
  • Spaces and punctuation may be ignored depending on requirements
  • Examples: “level,” “A man a plan a canal Panama,” “Was it a car or a cat I saw?”

Method 1: Two-Pointer Approach (Iterative)

This is the most common and efficient approach, using two pointers moving from opposite ends:

using System;

class PalindromeChecker
{
    public static bool IsPalindrome(string word)
    {
        if (string.IsNullOrEmpty(word))
            return true; // Empty string is considered a palindrome

        int left = 0;
        int right = word.Length - 1;

        while (left < right)
        {
            if (char.ToLower(word[left]) != char.ToLower(word[right]))
            {
                return false;
            }
            left++;
            right--;
        }

        return true;
    }

    static void Main()
    {
        string[] testWords = {
            "level", "hello", "radar", "civic", "deified", 
            "rotator", "madam", "world", "racecar", "noon"
        };

        Console.WriteLine("=== Palindrome Checker Results ===\n");

        foreach (string word in testWords)
        {
            bool isPalindrome = IsPalindrome(word);
            string result = isPalindrome ? "✓ Palindrome" : "✗ Not a palindrome";
            Console.WriteLine($"{word,-10}{result}");
        }

        Console.ReadKey();
    }
}

Time Complexity: O(n)
Space Complexity: O(1)

Method 2: Recursive Approach

A elegant recursive solution that demonstrates the mathematical nature of palindromes:

using System;

class RecursivePalindromeChecker
{
    public static bool IsPalindromeRecursive(string word)
    {
        if (string.IsNullOrEmpty(word))
            return true;

        return CheckPalindromeRecursive(word, 0, word.Length - 1);
    }

    private static bool CheckPalindromeRecursive(string word, int left, int right)
    {
        // Base case: single character or crossed pointers
        if (left >= right)
            return true;

        // Check current characters
        if (char.ToLower(word[left]) != char.ToLower(word[right]))
            return false;

        // Recursive call with inner characters
        return CheckPalindromeRecursive(word, left + 1, right - 1);
    }

    public static void TestRecursiveMethod()
    {
        string[] words = { "level", "hello", "civic", "world", "radar" };
        
        Console.WriteLine("=== Recursive Palindrome Check ===\n");
        
        foreach (string word in words)
        {
            bool result = IsPalindromeRecursive(word);
            Console.WriteLine($"{word}{(result ? "Palindrome" : "Not palindrome")}");
        }
    }
}

Method 3: LINQ-Based Approach

Using LINQ for a more functional programming style:

using System;
using System.Linq;

class LinqPalindromeChecker
{
    public static bool IsPalindromeLinq(string word)
    {
        if (string.IsNullOrEmpty(word))
            return true;

        var normalized = word.ToLower();
        return normalized.SequenceEqual(normalized.Reverse());
    }

    // Alternative LINQ approach using Zip
    public static bool IsPalindromeLinqZip(string word)
    {
        if (string.IsNullOrEmpty(word))
            return true;

        var normalized = word.ToLower().ToArray();
        var reversed = normalized.Reverse().ToArray();
        
        return normalized.Zip(reversed, (a, b) => a == b).All(x => x);
    }

    public static void TestLinqMethods()
    {
        string[] words = { "level", "programming", "civic", "algorithm" };
        
        Console.WriteLine("=== LINQ Palindrome Check ===\n");
        
        foreach (string word in words)
        {
            bool result1 = IsPalindromeLinq(word);
            bool result2 = IsPalindromeLinqZip(word);
            
            Console.WriteLine($"{word} → SequenceEqual: {result1}, Zip: {result2}");
        }
    }
}

Method 4: Advanced Palindrome Checker

A more sophisticated version that handles phrases, ignores punctuation, and provides detailed analysis:

using System;
using System.Linq;
using System.Text.RegularExpressions;

class AdvancedPalindromeChecker
{
    public static bool IsPhrasepalindrome(string phrase)
    {
        if (string.IsNullOrEmpty(phrase))
            return true;

        // Remove non-alphanumeric characters and convert to lowercase
        string cleaned = Regex.Replace(phrase, @"[^a-zA-Z0-9]", "").ToLower();
        
        return IsPalindrome(cleaned);
    }

    public static bool IsPalindrome(string word)
    {
        if (string.IsNullOrEmpty(word))
            return true;

        int left = 0;
        int right = word.Length - 1;

        while (left < right)
        {
            if (word[left] != word[right])
                return false;
            
            left++;
            right--;
        }

        return true;
    }

    public static PalindromeAnalysis AnalyzePalindrome(string input)
    {
        var analysis = new PalindromeAnalysis
        {
            OriginalInput = input,
            IsPalindrome = IsPhrasepalindrome(input),
            CleanedInput = Regex.Replace(input, @"[^a-zA-Z0-9]", "").ToLower(),
            Length = input?.Length ?? 0
        };

        return analysis;
    }

    public static void TestAdvancedMethod()
    {
        string[] phrases = {
            "level",
            "A man a plan a canal Panama",
            "race a car",
            "Was it a car or a cat I saw?",
            "Madam, I'm Adam",
            "Not a palindrome",
            "No 'x' in Nixon"
        };

        Console.WriteLine("=== Advanced Palindrome Analysis ===\n");

        foreach (string phrase in phrases)
        {
            var analysis = AnalyzePalindrome(phrase);
            
            Console.WriteLine($"Original: \"{analysis.OriginalInput}\"");
            Console.WriteLine($"Cleaned:  \"{analysis.CleanedInput}\"");
            Console.WriteLine($"Result:   {(analysis.IsPalindrome ? "✓ Palindrome" : "✗ Not palindrome")}");
            Console.WriteLine($"Length:   {analysis.Length} characters\n");
        }
    }
}

public class PalindromeAnalysis
{
    public string OriginalInput { get; set; }
    public string CleanedInput { get; set; }
    public bool IsPalindrome { get; set; }
    public int Length { get; set; }
}

Method 5: Performance-Optimized Version

For high-performance scenarios where you need to check many palindromes:

using System;
using System.Runtime.CompilerServices;

class PerformancePalindromeChecker
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsPalindromeOptimized(ReadOnlySpan<char> word)
    {
        int length = word.Length;
        int halfLength = length / 2;

        for (int i = 0; i < halfLength; i++)
        {
            if (char.ToLowerInvariant(word[i]) != char.ToLowerInvariant(word[length - 1 - i]))
            {
                return false;
            }
        }

        return true;
    }

    // For string input
    public static bool IsPalindromeOptimized(string word)
    {
        if (string.IsNullOrEmpty(word))
            return true;

        return IsPalindromeOptimized(word.AsSpan());
    }

    public static void TestPerformanceMethod()
    {
        string[] words = { "level", "radar", "civic", "hello", "world", "racecar" };
        
        Console.WriteLine("=== Performance-Optimized Palindrome Check ===\n");

        var startTime = DateTime.UtcNow;

        for (int iteration = 0; iteration < 100000; iteration++)
        {
            foreach (string word in words)
            {
                IsPalindromeOptimized(word);
            }
        }

        var endTime = DateTime.UtcNow;
        var elapsed = endTime - startTime;

        Console.WriteLine($"Processed {words.Length * 100000:N0} palindrome checks");
        Console.WriteLine($"Time elapsed: {elapsed.TotalMilliseconds:F2} ms");
        
        // Show results for verification
        foreach (string word in words)
        {
            bool result = IsPalindromeOptimized(word);
            Console.WriteLine($"{word}{(result ? "Palindrome" : "Not palindrome")}");
        }
    }
}

Complete Example with User Input

A comprehensive example that combines multiple approaches:

using System;
using System.Collections.Generic;
using System.Linq;

class CompletePalindromeProgram
{
    static void Main()
    {
        Console.WriteLine("=== Palindrome Checker Program ===\n");

        // Pre-defined test cases
        RunTestCases();

        // Interactive mode
        RunInteractiveMode();
    }

    static void RunTestCases()
    {
        var testCases = new Dictionary<string, bool>
        {
            { "level", true },
            { "radar", true },
            { "civic", true },
            { "deified", true },
            { "rotator", true },
            { "madam", true },
            { "racecar", true },
            { "noon", true },
            { "hello", false },
            { "world", false },
            { "programming", false },
            { "algorithm", false },
            { "", true }, // Edge case: empty string
            { "a", true }, // Edge case: single character
            { "ab", false }
        };

        Console.WriteLine("Running test cases...\n");

        int passed = 0;
        int total = testCases.Count;

        foreach (var testCase in testCases)
        {
            bool result = PalindromeChecker.IsPalindrome(testCase.Key);
            bool expected = testCase.Value;
            string status = result == expected ? "✓ PASS" : "✗ FAIL";
            
            Console.WriteLine($"{testCase.Key,-12} → Expected: {expected,-5} Got: {result,-5} {status}");
            
            if (result == expected) passed++;
        }

        Console.WriteLine($"\nTest Results: {passed}/{total} passed ({(double)passed/total*100:F1}%)\n");
    }

    static void RunInteractiveMode()
    {
        Console.WriteLine("=== Interactive Mode ===");
        Console.WriteLine("Enter words to check (type 'exit' to quit):\n");

        while (true)
        {
            Console.Write("Enter word: ");
            string input = Console.ReadLine();

            if (string.IsNullOrWhiteSpace(input) || input.ToLower() == "exit")
                break;

            // Check using different methods
            bool iterative = PalindromeChecker.IsPalindrome(input);
            bool recursive = RecursivePalindromeChecker.IsPalindromeRecursive(input);
            bool linq = LinqPalindromeChecker.IsPalindromeLinq(input);
            bool advanced = AdvancedPalindromeChecker.IsPhrasepalindrome(input);

            Console.WriteLine($"\nResults for '{input}':");
            Console.WriteLine($"  Iterative:  {(iterative ? "Palindrome" : "Not palindrome")}");
            Console.WriteLine($"  Recursive:  {(recursive ? "Palindrome" : "Not palindrome")}");
            Console.WriteLine($"  LINQ:       {(linq ? "Palindrome" : "Not palindrome")}");
            Console.WriteLine($"  Advanced:   {(advanced ? "Palindrome" : "Not palindrome")}");
            Console.WriteLine();
        }

        Console.WriteLine("Thanks for using the Palindrome Checker!");
        Console.ReadKey();
    }
}

Performance Comparison

Different methods have varying performance characteristics:

Method Time Complexity Space Complexity Best For
Two-Pointer O(n) O(1) General use, best performance
Recursive O(n) O(n) Educational, functional style
LINQ O(n) O(n) Readable code, quick prototyping
Advanced O(n) O(n) Complex text processing
Optimized O(n) O(1) High-performance scenarios

Best Practices

  1. Handle Edge Cases: Empty strings, null values, single characters
  2. Case Sensitivity: Always normalize case for word comparison
  3. Unicode Considerations: Use ToLowerInvariant() for culture-independent comparison
  4. Memory Efficiency: Use ReadOnlySpan<char> for performance-critical code
  5. Input Validation: Check for null or empty inputs
  6. Choose the Right Method: Consider your specific requirements (performance vs. readability)

Real-World Applications

Palindrome detection is used in:

  • Data Validation: Checking for symmetric patterns
  • Bioinformatics: Finding palindromic DNA sequences
  • Cryptography: Certain encryption algorithms
  • Text Processing: Content analysis and pattern matching
  • Game Development: Word games and puzzles

Conclusion

Palindrome detection is a classic programming problem that demonstrates various algorithmic approaches. While the two-pointer iterative method offers the best balance of performance and readability, each approach has its merits depending on your specific requirements. Choose the method that best fits your needs in terms of performance, maintainability, and code style.

Comments