# Letters in the Noise

> Case and punctuation are distractions. Find what each letter really weighs.

Canonical URL: <https://datadriven.io/problems/letters_in_the_noise>

Domain: Python · Difficulty: easy · Seniority: L5

## Problem

A text-cleaning step in your pipeline needs a per-letter tally of the raw strings passing through it. For a given `s`, return the letter frequencies as `[letter, count]` pairs.

## Worked solution and explanation

### What this really is

Strip the costume and this is a case-folded letter tally, the simplest frequency map there is. The real question is whether you build a keyed count at all: a set can tell you which letters appeared but throws the counts away, and a plain list cannot key by letter. You need a dictionary keyed by the lowercased character. The one move that actually separates answers is lowercasing each letter before it becomes a key, so 'A' and 'a' land in the same bucket. Skip it and your counts split across cases and your output carries uppercase keys the lowercase contract forbids.

---

### Break down the requirements

#### Step 1: Lowercase before you key

Lowercase each alphabetic character so case-variants collapse into one bucket, and skip anything that is not a letter. The lowercasing has to happen before the character keys the map; do it afterward and the counts have already split into 'A' and 'a'.

#### Step 2: Count into a map

Tally into a dictionary keyed by the lowercase letter. One pass over the string, constant-time updates per character.

#### Step 3: Sort the keys, format the pairs

Walk the keys in sorted order and emit each as a [character, count] pair. Sorting the keys (at most 26 of them) is effectively free.

---

### The solution

**Frequency map with alphabetic filter and sorted output**

```python
def character_occurrence_map(s):
    counts = {}
    for char in s:
        if char.isalpha():
            lower_char = char.lower()
            counts[lower_char] = counts.get(lower_char, 0) + 1
    return [[key, counts[key]] for key in sorted(counts)]
```

> **Why this stays cheap**
>
> Time: O(n + k log k) where n is the string length and k is the number of unique letters (at most 26, so the sort is effectively constant). Space: O(k) for the map, at most 26 entries. Lowercasing and the alphabetic check are constant-time per character, so the single pass dominates.

> **Interviewers watch for**
>
> The tell is whether you fold case correctly. Tally the raw characters and 'A' and 'a' become two separate keys with split totals. Strong candidates lowercase each letter before it keys the map, so identical letters always land together no matter how they were typed.

> **Common pitfall**
>
> Forgetting to lowercase before counting. If 'A' and 'a' become separate keys, your counts are wrong AND the output contract (lowercase letters) is broken. Lowercase at the door, once, and both problems vanish.

---

## Common follow-up questions

- What if the output should be sorted by frequency descending instead? _(Tests whether you can change the sort key to use counts with a tiebreaker on character.)_
- How would you handle unicode characters beyond ASCII? _(Tests awareness of str.isalpha() behavior with non-Latin scripts.)_
- What if the input is a very large file read line by line? _(Tests streaming character counting without loading the entire string into memory.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/letters_in_the_noise)
- [Python Interview Questions](https://datadriven.io/python-interview-questions)
- [Data Engineering Interview Prep Guide](https://datadriven.io/data-engineer-interview-prep)
- [Daily Challenge](https://datadriven.io/daily)

---

Source: DataDriven (https://datadriven.io). DataDriven is the data engineering interview community. Live code execution in SQL, Python, and Spark sandboxes. Every feature is open to every member.