Home DSA Anagram and Palindrome Problems Explained — Java DSA Guide for Beginners

Anagram and Palindrome Problems Explained — Java DSA Guide for Beginners

In Plain English 🔥
An anagram is like taking the letters on a bunch of Scrabble tiles and rearranging them into a completely different word — 'listen' and 'silent' use the exact same tiles, just in a different order. A palindrome is like looking at a word in a mirror — 'racecar' looks the same forwards and backwards. These aren't just word games; they're patterns that show up constantly in programming problems because they force you to think about how data is structured and compared.
⚡ Quick Answer
An anagram is like taking the letters on a bunch of Scrabble tiles and rearranging them into a completely different word — 'listen' and 'silent' use the exact same tiles, just in a different order. A palindrome is like looking at a word in a mirror — 'racecar' looks the same forwards and backwards. These aren't just word games; they're patterns that show up constantly in programming problems because they force you to think about how data is structured and compared.

Every tech interview on the planet has at least one string manipulation problem, and anagrams and palindromes are the two most popular flavours. Google uses palindrome-style logic in DNA sequence analysis. Spell checkers use anagram detection to suggest corrections. Even cybersecurity tools use these ideas to detect scrambled spam keywords. The reason they're so popular isn't to torture you — it's because solving them correctly requires you to understand how characters are stored, how to count efficiently, and how to think about equality in a smarter way than just ==.

The problem they solve is deceptively simple: given one or two strings, can you determine a relationship between their characters without brute-forcing every possible combination? A naive approach to checking if two strings are anagrams might compare every permutation — that explodes to factorial time complexity for long strings. The clever approaches you'll learn here run in linear time, which is why interviewers love them.

By the end of this article you'll be able to write clean, efficient Java solutions for both anagram and palindrome problems from scratch. You'll understand exactly why each approach works, spot the subtle bugs that trip up 80% of beginners, and confidently walk through your thought process in an interview setting.

What Is an Anagram — And How Do You Detect One in Code?

Two strings are anagrams if they contain exactly the same characters in exactly the same frequencies — just arranged differently. 'Astronomer' and 'Moon starer' are anagrams (ignoring spaces). 'Cat' and 'Car' are NOT — because 't' and 'r' are different characters.

The core insight is this: if you count how many times each letter appears in both strings, an anagram will produce identical frequency counts. That's your algorithm.

The simplest way to implement this in Java is with a frequency array of size 26 — one slot for each letter of the alphabet. You increment the count for each character in the first string, then decrement for each character in the second string. If every slot ends up at zero, they're anagrams. This runs in O(n) time and O(1) space — because the array is always size 26, no matter how long the strings are.

Why not just sort both strings and compare? You can — and it works — but sorting costs O(n log n). The frequency array approach is strictly faster. For small strings it barely matters, but in a system processing millions of queries per second, that difference is huge. Good engineers think about this from the start.

AnagramChecker.java · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
public class AnagramChecker {

    /**
     * Checks if two strings are anagrams of each other.
     * Ignores case, ignores spaces — just like real word puzzles.
     *
     * Time Complexity:  O(n) — we scan each string once
     * Space Complexity: O(1) — fixed-size array of 26, regardless of input size
     */
    public static boolean areAnagrams(String firstWord, String secondWord) {

        // Step 1: Normalise both strings — lowercase, no spaces
        // This means "Listen" and "Silent" will still match
        String cleanFirst  = firstWord.toLowerCase().replaceAll("\\s", "");
        String cleanSecond = secondWord.toLowerCase().replaceAll("\\s", "");

        // Step 2: Quick sanity check — if lengths differ after cleaning,
        // they can't possibly be anagrams (different total character counts)
        if (cleanFirst.length() != cleanSecond.length()) {
            return false;
        }

        // Step 3: Create a frequency array — 26 slots, one per letter a-z
        // All slots start at 0 by default in Java
        int[] letterFrequency = new int[26];

        // Step 4: Walk through the first word, incrementing the count
        // for each character we encounter.
        // 'a' has ASCII value 97. Subtracting 'a' maps 'a'->0, 'b'->1, etc.
        for (char letter : cleanFirst.toCharArray()) {
            letterFrequency[letter - 'a']++;
        }

        // Step 5: Walk through the second word, DECREMENTING the count.
        // If the second word has a letter not in the first, its slot goes negative.
        for (char letter : cleanSecond.toCharArray()) {
            letterFrequency[letter - 'a']--;
        }

        // Step 6: If every slot is exactly zero, the frequencies matched perfectly.
        // Any non-zero slot means one word had more of some letter than the other.
        for (int frequency : letterFrequency) {
            if (frequency != 0) {
                return false; // Mismatch found — not an anagram
            }
        }

        return true; // All frequencies balanced out — they ARE anagrams!
    }

    public static void main(String[] args) {

        // Test case 1: Classic anagram pair
        System.out.println(areAnagrams("listen", "silent"));       // true

        // Test case 2: Anagram with mixed case
        System.out.println(areAnagrams("Astronomer", "Moon starer")); // true

        // Test case 3: Not an anagram — 't' vs 'r'
        System.out.println(areAnagrams("cat", "car"));              // false

        // Test case 4: Different lengths
        System.out.println(areAnagrams("hello", "helloo"));         // false

        // Test case 5: Single characters that match
        System.out.println(areAnagrams("a", "a"));                  // true
    }
}
▶ Output
true
true
false
false
true
⚠️
Pro Tip: The 'a' Subtraction TrickThe expression `letter - 'a'` is a classic character-to-index mapping. In Java, chars are just numbers under the hood. 'a' is 97, 'b' is 98, so 'b' - 'a' = 1. This maps any lowercase letter directly to a 0-25 index. You'll see this pattern in dozens of string problems — memorise it.

What Is a Palindrome — And the Two-Pointer Trick That Makes It Easy

A palindrome reads the same forwards and backwards. 'Racecar'. 'Madam'. 'A man a plan a canal Panama' (if you strip spaces and ignore case). The concept is beautifully simple, but there's a subtle trap: do you handle even-length and odd-length palindromes differently?

The answer with the two-pointer technique is no — and that's what makes it elegant. You place one pointer at the very start of the string and one at the very end. You compare the characters those pointers are pointing to. If they match, you move both pointers inward — left pointer goes right, right pointer goes left. If they ever don't match, it's not a palindrome. You stop when the pointers meet or cross.

For 'racecar' (7 characters, odd length): r==r ✓, a==a ✓, c==c ✓, middle 'e' is never compared against anything. For 'abba' (4 characters, even length): a==a ✓, b==b ✓, pointers cross, done. Both cases handled by the exact same loop.

This is O(n) time and O(1) space — you're not creating any new data structures, just using two integer indices as pointers. This matters in memory-constrained environments.

PalindromeChecker.java · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
public class PalindromeChecker {

    /**
     * Checks if a string is a palindrome using the two-pointer technique.
     * Ignores spaces and case so real phrases like "Race a car" work correctly.
     *
     * Time Complexity:  O(n) — each character is visited at most once
     * Space Complexity: O(n) — only for the cleaned string; logic itself is O(1)
     */
    public static boolean isPalindrome(String inputText) {

        // Step 1: Clean the input — remove spaces, make lowercase
        // We want "A man a plan a canal Panama" to return true
        String cleanedText = inputText.toLowerCase().replaceAll("[^a-z0-9]", "");

        // Step 2: Edge case — an empty string or single character is always a palindrome
        if (cleanedText.length() <= 1) {
            return true;
        }

        // Step 3: Set up two pointers — one at each end of the string
        int leftIndex  = 0;
        int rightIndex = cleanedText.length() - 1;

        // Step 4: March the pointers toward the centre
        // They stop when they meet (odd length) or cross (even length)
        while (leftIndex < rightIndex) {

            char leftChar  = cleanedText.charAt(leftIndex);
            char rightChar = cleanedText.charAt(rightIndex);

            // Step 5: If the characters don't match — it's NOT a palindrome.
            // We can exit immediately, no need to check further.
            if (leftChar != rightChar) {
                return false;
            }

            // Step 6: Characters matched — move both pointers one step inward
            leftIndex++;
            rightIndex--;
        }

        // Step 7: If we made it through the whole loop without returning false,
        // every mirrored pair matched — it IS a palindrome!
        return true;
    }

    /**
     * BONUS: Check if a string is a palindrome by comparing it to its reverse.
     * Simpler to read, but uses O(n) extra space for the reversed string.
     * Great to mention in an interview as the naive approach before optimising.
     */
    public static boolean isPalindromeNaive(String inputText) {
        String cleanedText = inputText.toLowerCase().replaceAll("[^a-z0-9]", "");
        String reversedText = new StringBuilder(cleanedText).reverse().toString();
        // Direct string comparison — true only if they're identical
        return cleanedText.equals(reversedText);
    }

    public static void main(String[] args) {

        // Test case 1: Classic odd-length palindrome
        System.out.println(isPalindrome("racecar"));                         // true

        // Test case 2: Classic even-length palindrome
        System.out.println(isPalindrome("abba"));                            // true

        // Test case 3: Famous phrase palindrome (spaces and mixed case ignored)
        System.out.println(isPalindrome("A man a plan a canal Panama"));     // true

        // Test case 4: Not a palindrome
        System.out.println(isPalindrome("hello"));                           // false

        // Test case 5: Single character
        System.out.println(isPalindrome("z"));                               // true

        // Test case 6: Naive approach for comparison
        System.out.println(isPalindromeNaive("Madam"));                      // true
    }
}
▶ Output
true
true
true
false
true
true
🔥
Interview Gold: Always Clarify the InputBefore writing a single line of code in an interview, ask: 'Should I ignore spaces and punctuation? Is the check case-sensitive?' This shows professional instinct. Most interviewers expect you to handle 'A man a plan a canal Panama' as a valid palindrome, but they want YOU to ask — not assume.

Levelling Up — Grouping Anagrams and Finding the Longest Palindromic Substring

Once you're comfortable with the basics, interviews throw harder variants at you. The two most common are: (1) Given a list of words, group all the anagrams together. (2) Find the longest substring inside a string that is itself a palindrome.

For grouping anagrams, the key insight is that if you sort the letters of any word, all its anagrams produce the same sorted string — that sorted string becomes a key in a HashMap. 'eat', 'tea', 'ate' all sort to 'aet'. You group words by their sorted key.

For the longest palindromic substring, the classic approach is 'expand from centre'. For every character in the string (and every gap between characters for even-length palindromes), you expand outward as long as the characters on both sides match. You track the longest expansion you found. This runs in O(n²) time — acceptable for interview settings. There's a fancier O(n) algorithm called Manacher's, but interviewers don't expect it unless you're at a top-tier FAANG role.

These two problems show up in the same interview as 'easy/medium' pairs. Nail them both.

AdvancedStringProblems.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
import java.util.*;

public class AdvancedStringProblems {

    // ─────────────────────────────────────────────────────────────
    // PROBLEM 1: Group Anagrams
    // Input:  ["eat", "tea", "tan", "ate", "nat", "bat"]
    // Output: [["eat","tea","ate"], ["tan","nat"], ["bat"]]
    // ─────────────────────────────────────────────────────────────

    public static List<List<String>> groupAnagrams(String[] words) {

        // A HashMap where:
        //   key   = the sorted version of a word (e.g. "aet" for "eat","tea","ate")
        //   value = the list of original words that sort to that key
        Map<String, List<String>> anagramGroups = new HashMap<>();

        for (String word : words) {

            // Sort the characters of the word to create a canonical key
            // "eat" -> char array ['e','a','t'] -> sorted ['a','e','t'] -> "aet"
            char[] wordCharacters = word.toCharArray();
            Arrays.sort(wordCharacters);
            String sortedKey = new String(wordCharacters);

            // If this key doesn't exist in the map yet, create a new list for it
            // computeIfAbsent is cleaner than a manual if-null check
            anagramGroups.computeIfAbsent(sortedKey, k -> new ArrayList<>());

            // Add the original word to its anagram group
            anagramGroups.get(sortedKey).add(word);
        }

        // Return all the groups as a list of lists
        return new ArrayList<>(anagramGroups.values());
    }

    // ─────────────────────────────────────────────────────────────
    // PROBLEM 2: Longest Palindromic Substring
    // Input:  "babad"
    // Output: "bab" (or "aba" — both are valid)
    //
    // Input:  "cbbd"
    // Output: "bb"
    // ─────────────────────────────────────────────────────────────

    public static String longestPalindromicSubstring(String inputText) {

        if (inputText == null || inputText.length() < 1) return "";

        // Track the start and end indices of the longest palindrome found
        int longestStart  = 0;
        int longestEnd    = 0;

        for (int centreIndex = 0; centreIndex < inputText.length(); centreIndex++) {

            // Case 1: Odd-length palindromes (single character centre)
            // e.g. "racecar" — centre is 'e'
            int oddLength = expandFromCentre(inputText, centreIndex, centreIndex);

            // Case 2: Even-length palindromes (gap between two characters as centre)
            // e.g. "abba" — centre is the gap between the two b's
            int evenLength = expandFromCentre(inputText, centreIndex, centreIndex + 1);

            // Take whichever expansion was longer
            int maxLengthAtThisCentre = Math.max(oddLength, evenLength);

            // Check if this is longer than the best we've found so far
            if (maxLengthAtThisCentre > longestEnd - longestStart + 1) {
                // Back-calculate the start and end indices from the length
                // (integer division handles both odd and even lengths correctly)
                longestStart = centreIndex - (maxLengthAtThisCentre - 1) / 2;
                longestEnd   = centreIndex + maxLengthAtThisCentre / 2;
            }
        }

        // Extract and return the actual substring
        return inputText.substring(longestStart, longestEnd + 1);
    }

    /**
     * Helper: Expands outward from a centre position as long as characters match.
     * Returns the LENGTH of the palindrome found.
     *
     * For odd palindromes:  call with (index, index)
     * For even palindromes: call with (index, index + 1)
     */
    private static int expandFromCentre(String text, int leftPointer, int rightPointer) {
        // Expand outward while within bounds AND characters still match
        while (leftPointer >= 0
                && rightPointer < text.length()
                && text.charAt(leftPointer) == text.charAt(rightPointer)) {
            leftPointer--;   // Move left pointer further left
            rightPointer++;  // Move right pointer further right
        }
        // When the loop ends, pointers have gone ONE step too far.
        // The actual palindrome length is rightPointer - leftPointer - 1.
        return rightPointer - leftPointer - 1;
    }

    public static void main(String[] args) {

        // ── Group Anagrams Demo ──
        String[] wordList = {"eat", "tea", "tan", "ate", "nat", "bat"};
        List<List<String>> groups = groupAnagrams(wordList);
        System.out.println("Anagram Groups:");
        for (List<String> group : groups) {
            System.out.println("  " + group);
        }

        System.out.println();

        // ── Longest Palindromic Substring Demo ──
        System.out.println("Longest palindrome in 'babad':  " + longestPalindromicSubstring("babad"));
        System.out.println("Longest palindrome in 'cbbd':   " + longestPalindromicSubstring("cbbd"));
        System.out.println("Longest palindrome in 'racecar': " + longestPalindromicSubstring("racecar"));
        System.out.println("Longest palindrome in 'abacaba': " + longestPalindromicSubstring("abacaba"));
    }
}
▶ Output
Anagram Groups:
[eat, tea, ate]
[tan, nat]
[bat]

Longest palindrome in 'babad': bab
Longest palindrome in 'cbbd': bb
Longest palindrome in 'racecar': racecar
Longest palindrome in 'abacaba': abacaba
⚠️
Watch Out: Even-Length Palindromes Are a Hidden BugA huge percentage of beginners only handle odd-length palindromes in the 'expand from centre' approach. Their code works on 'racecar' but silently fails on 'abba'. Always run BOTH expansions — `(i, i)` for odd and `(i, i+1)` for even — at every centre position. This is the single most common reason palindrome solutions get rejected.
AspectFrequency Array ApproachSort-and-Compare Approach
Time ComplexityO(n) — single pass through each stringO(n log n) — sorting dominates
Space ComplexityO(1) — fixed 26-slot array alwaysO(n) — sorted copy of string created
Code SimplicityModerate — requires index mapping trickVery simple — Arrays.sort + equals()
Works for Unicode?No — only handles 26 English letters as-isYes — Arrays.sort handles any char set
Best forPerformance-critical / interview optimal answerQuick solution or non-ASCII characters
Handles duplicates?Yes — frequency count tracks exact quantitiesYes — sorted duplicates stay adjacent

🎯 Key Takeaways

  • The frequency array trick (26 slots, letter - 'a' indexing) solves anagram detection in O(n) time and O(1) space — memorise this pattern cold.
  • The two-pointer technique for palindrome checking is O(n) time O(1) space — left pointer starts at 0, right at length-1, they march inward until they meet or a mismatch is found.
  • Always run BOTH the odd-centre (i, i) and even-centre (i, i+1) expansions in the expand-from-centre palindrome approach — missing the even case is the #1 silent bug.
  • Normalise your strings first — lowercase and strip spaces/punctuation — BEFORE running any anagram or palindrome logic. Skipping this step causes subtle failures on real-world inputs.

⚠ Common Mistakes to Avoid

  • Mistake 1: Using == instead of .equals() to compare strings — Your isPalindrome check returns wrong results even when strings look identical — Always use .equals() for String content comparison in Java. == checks if they're the same object in memory, not if they have the same content. cleanedText == reversedText will almost always be false even for 'racecar'.
  • Mistake 2: Forgetting to normalise case and spaces before checking — 'Racecar' returns false, 'A man a plan a canal Panama' returns false — Always call .toLowerCase() and strip non-alphanumeric characters with .replaceAll('[^a-z0-9]', '') BEFORE running your palindrome or anagram logic. Normalise first, check second.
  • Mistake 3: Only checking odd-length palindromes in the expand-from-centre approach — 'abba', 'deed', 'noon' all incorrectly return a single character as the longest palindrome — Make sure you run expandFromCentre(i, i) AND expandFromCentre(i, i+1) for every index. The first handles odd-length, the second handles even-length. Both calls are required every single iteration.

Interview Questions on This Topic

  • QGiven a string, determine if it is a valid palindrome considering only alphanumeric characters and ignoring case — what is your approach and what is its time and space complexity?
  • QGiven an array of strings, group all anagrams together and return them as a list of lists — walk me through your solution and explain why you chose that data structure.
  • QHow would you find ALL palindromic substrings in a string, not just the longest one? What changes in your approach — and what is the total time complexity now?

Frequently Asked Questions

What is the difference between an anagram and a palindrome?

An anagram is a relationship between TWO strings — one is a rearrangement of the other ('listen' and 'silent'). A palindrome is a property of ONE string — it reads the same forwards and backwards ('racecar'). They're completely different concepts that just happen to both involve rearranging characters.

Is every palindrome an anagram of itself?

Yes, technically — any string is an anagram of itself since it contains the same characters with the same frequencies. But the more interesting question is whether the reverse of a palindrome is an anagram of the original. It always is, because the reverse contains identical characters. This is a fun edge case worth mentioning in interviews to show deep thinking.

Why use a frequency array instead of a HashMap for anagram checking?

When the input is strictly lowercase English letters (a-z), the frequency array is faster and uses less memory. A HashMap has hashing overhead and stores boxed Integer objects. The array is a fixed 26 slots of primitive ints. If you're dealing with Unicode, full ASCII, or arbitrary character sets, switch to a HashMap — but always state your assumption about the character set to your interviewer.

🔥
TheCodeForge Editorial Team Verified Author

Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.

← PreviousString Manipulation PatternsNext →Matrix Traversal Patterns
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged