String Problems in Python for Coding Rounds: Palindromes, Anagrams and Compression Solved

Python turns common string problems into one-liners, but interviewers usually remove that shortcut next. Learn the Pythonic and manual solutions for three recurring patterns.

KnowledgeGate Team

Exam prep & CS education

Updated 15 Jul 20266 min read

String questions dominate the easy-to-medium slot of many coding rounds, and Python makes them look deceptively short. You can check a palindrome with s == s[::-1] and compare anagrams with Counter, but the follow-up is often, "Now do it without slicing or built-ins." You need the quick Pythonic form and the manual logic underneath it. Palindromes, anagrams, and run-length compression are the three patterns that recur most, and each has a short Python form and a manual form worth carrying into the room.

The three problems and the two versions you must know

Palindrome checking, anagram testing, and run-length compression rest on a small set of reusable moves: scanning from both ends, counting occurrences, and grouping adjacent equal characters. The syntax is not the hard part. The interviewer wants to know whether you can state the assumptions, choose a suitable approach, and defend its time and space cost.

For each problem, prepare two versions:

  1. A concise Pythonic solution that is fast to write in a timed round

  2. A manual solution that exposes the algorithm when slicing, Counter, or other helpers are banned

The second version proves that you understand the pattern rather than remembering a trick. It also prepares you for language-independent questions. The placement preparation category carries the courses that put these drills inside a full coding-round plan.

Palindrome: two-pointer and the normalise trap

A palindrome reads the same from left to right and right to left. The direct Python check is:

def is_palindrome_pythonic(s: str) -> bool:
    return s == s[::-1]

Slicing creates a reversed string, so this takes O(n) time and O(n) extra space. When extra space matters, use two pointers:

def is_palindrome(s: str) -> bool:
    left = 0
    right = len(s) - 1

    while left < right:
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1

    return True

For s = "racecar", the scan is exact:

  • left = 0, right = 6: r == r

  • left = 1, right = 5: a == a

  • left = 2, right = 4: c == c

  • Both pointers reach index 3, the middle e, without a mismatch

Therefore, "racecar" is a palindrome. The loop compares at most n/2 pairs, which is still O(n) time, and it uses O(1) extra space.

The real trap is the input rule. If punctuation, spaces, and case do not matter, normalise first:

def normalised_palindrome(s: str) -> bool:
    clean = "".join(ch.lower() for ch in s if ch.isalnum())
    return is_palindrome(clean)

"A man, a plan, a canal: Panama" becomes "amanaplanacanalpanama", which passes the two-pointer check. Without normalisation, the first uppercase letter, spaces, and punctuation would make a naive comparison fail. Ask whether case and punctuation count before you code.

Two-pointer scan of the word racecar, with left and right pointers moving inward and matching each letter pair to confirm a palindrome.

Anagram: Counter equality and the O(n) vs O(n log n) choice

Two strings are anagrams when they contain the same characters with the same frequencies. Python's Counter expresses that rule directly:

from collections import Counter

def are_anagrams_pythonic(a: str, b: str) -> bool:
    return Counter(a) == Counter(b)

For "listen" and "silent", both frequency maps contain l:1, i:1, s:1, t:1, e:1, and n:1. The maps are equal, so the answer is True. For "rat" and "car", the character counts differ, so the answer is False.

For lowercase English letters, the manual version can use a fixed array:

def are_anagrams(a: str, b: str) -> bool:
    if len(a) != len(b):
        return False

    counts = [0] * 26
    base = ord("a")

    for ch in a:
        counts[ord(ch) - base] += 1
    for ch in b:
        counts[ord(ch) - base] -= 1

    return all(count == 0 for count in counts)

The length check rejects impossible pairs early. Every character in the first string adds one, every character in the second subtracts one, and all zeros mean perfect cancellation. This is O(n) time and O(1) auxiliary space because the array size stays 26. If the alphabet is not fixed, use a dictionary and call the space cost O(k), where k is the number of distinct characters.

You could also write sorted(a) == sorted(b), but sorting takes O(n log n) time and builds sorted results. Counter is normally the clearer O(n) choice. Decide whether to lower-case or remove spaces before counting because that changes the problem itself.

String compression: run-length with the length guard

Run-length compression replaces a consecutive run with the character and its count. Both versions below must return the original string unless the compressed result is shorter. The Pythonic form leans on itertools.groupby, which hands you each run already grouped:

from itertools import groupby

def compress_pythonic(s: str) -> str:
    compressed = "".join(ch + str(len(list(group))) for ch, group in groupby(s))
    return compressed if len(compressed) < len(s) else s

groupby is exactly the helper an interviewer removes next, so keep the manual scan ready:

def compress(s: str) -> str:
    if not s:
        return s

    parts = []
    run_length = 1

    for i in range(1, len(s)):
        if s[i] == s[i - 1]:
            run_length += 1
        else:
            parts.append(s[i - 1])
            parts.append(str(run_length))
            run_length = 1

    parts.append(s[-1])
    parts.append(str(run_length))

    compressed = "".join(parts)
    return compressed if len(compressed) < len(s) else s

Work through the manual scan on s = "aaabbbccccd":

  • The a run has length 3, so emit a3.

  • The b run has length 3, so emit b3.

  • The c run has length 4, so emit c4.

  • The final d run has length 1, so emit d1.

  • The result is "a3b3c4d1". Its length is 2 + 2 + 2 + 2 = 8.

  • The original length is 3 + 3 + 4 + 1 = 11.

  • Since 8 < 11, return "a3b3c4d1".

Now try s = "abcdef". Every run has length 1, so the candidate is "a1b1c1d1e1f1". Six character-count pairs take 6 × 2 = 12 positions, while the original has length 6. Since 12 is not less than 6, return the original "abcdef".

Run-length scan of aaabbbccccd tracking each character run and emitting a3b3c4d1, which is shorter than the original string.

The scan is O(n). The output and joined compressed string use O(n) space in the worst case. A two-pass variant can calculate the compressed length first, but Python strings are immutable, so returning a new string still requires output storage.

The traps coding rounds set

The most common compression bug is forgetting the final flush. Runs are normally emitted when the next character differs, but the last run has no next character. That is why the two parts.append calls appear after the loop.

Other traps are equally predictable:

  • s[::-1] creates a new string, so it is not an O(1)-space answer.

  • Counter creates a frequency object, so it is not an in-place solution.

  • Repeated out += piece can lead to quadratic work because strings are immutable. Collect pieces in a list, then call "".join(parts) once.

  • An empty string and a single-character string are palindromes.

  • Compression should handle the empty string, a single character, and one long run.

  • A 26-slot anagram array is valid only when the character set has been restricted to lowercase English letters.

If slicing, aliasing, or mutability details still cause surprises, work through Python output-based questions before your next round.

How interviewers push past the one-liner

After accepting the short solution, an interviewer may ban slicing, ban Counter, ask for in-place work, or demand the complexity. They may also probe inputs such as "", "a", "aaaa", mixed case, spaces, or Unicode. State what you assume, then adapt only what the clarified problem requires.

KnowledgeGate's question bank carries more than 450 Python questions, including a Collections and Strings set that drills these patterns. Combine that volume with the timing and attempt-order advice in coding round strategy for placements, rather than solving isolated questions without a round plan.

Short version and next step

For a palindrome, normalise as required and use two pointers. For an anagram, compare frequency counts in O(n) time. For compression, count adjacent runs, flush the last run, and return the compressed result only when it is shorter. Know the Pythonic version and the manual version of each.

Run timed sets in DSA Using Python, then connect them to the wider Mera Placement Hoga track. The goal is not to memorise three answers. It is to recognise three reusable string patterns quickly.