Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Lowercase the sentence, then check whether all 26 letters a–z appear at least once. Drop each letter into a set (or flip a bit in a 26-slot boolean array) and at the end ask: did I collect 26 distinct letters? If yes it's a pangram, otherwise it isn't. One pass, O(n) time, O(1) space because the alphabet is fixed at 26.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you recognise that a known 26-letter alphabet means you can swap a hash set for a fixed-size boolean array (or a bitmask) and get O(1) space, and whether you remember to normalise case and skip non-letters.
Where candidates slip
Forgetting to lowercase the input, so 'The quick...' misses letters that only appear capitalised, or indexing spaces/punctuation into the array and reading out of bounds.
How to stand out
Offer the bitmask version: a single 32-bit int where bit k means 'letter k seen', then check the mask equals (1 << 26) - 1. It's the same idea with one integer instead of an array.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewSentence sIs char a letter a–z?NoYesExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Per-Letter Rescan (Brute Force)
1Hint 1Explore hintHide hint
The definition is literally 'every letter a–z appears'. How would you check that definition one letter at a time?
2Hint 2Explore hintHide hint
For each letter from 'a' to 'z', scan the whole sentence and ask 'does this letter appear anywhere?'
3Hint 3Explore hintHide hint
If any of the 26 letters is absent from a full scan, return 'not pangram'. If all 26 survive their scans, it's a pangram.
A · Core idea
The most direct translation of the definition: a pangram contains every letter, so check the letters one by one. For each character c from a to z, walk the (lowercased) sentence and see whether c shows up anywhere. The moment one letter is missing, you can answer 'not pangram'.
Why it is obviously correct. It mirrors the definition word for word. 'Contains every letter' becomes a loop over every letter; 'contains' becomes a scan for that letter. There is no insight to miss, which is exactly why it's a good opener — you prove you understand the requirement before optimising.
Why we use case-folding once up front. Because the match is case-insensitive, lowercase the sentence (or compare case-insensitively) so a capital Q still satisfies the search for q. Skip this and 'Quick brown fox...' fails to find a lowercase q.
Why we don't ship it. You read the same characters up to 26 times. For n = 10⁵ that's 2.6 million character comparisons doing redundant work — you re-discover that e is present long after you first saw it. A single pass that records each letter the first time it appears collapses all 26 scans into one. Mention this version, state the O(26·n) cost, then improve it.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "abc"letter 'a'
scan "abc" → found
still possiblenot pangramC · Code with teaching comments
Reference implementation previewpublic String pangrams(String s) { String lower = s.toLowerCase(); for (char c = 'a'; c <= 'z'; c++) { if (lower.indexOf(c) == -1) { return "not pangram";Explore the full codeHide the full code
D · Why it works and complexityO(26·n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Each of the 26 scans throws away everything it learned. The fix is to remember which letters you've already seen so you never look for the same letter twice — that single observation turns 26 passes into one.
ComplexityO(26·n) time, O(1) space — Twenty-six independent passes over the string, each O(n), gives O(26·n). Only a couple of loop variables are stored → O(1) extra space.
E · Edge cases and pitfalls3Forgetting to lowercase, so capital-only letters look missing.Explore edge casesHide edge cases
Pitfalls
- Forgetting to lowercase, so capital-only letters look missing.
- Re-scanning the string 26 times instead of recording letters in one pass.
- Using indexOf inside the loop and assuming it's O(1) — it's O(n) each call.
Approach 2 of 2 · Optimal
Single-Pass Presence Set / boolean[26] (Optimal)
1Hint 1Explore hintHide hint
Instead of asking 26 times 'is this letter present?', flip the question: scan once and record every letter you meet. What small structure remembers 'seen' for a 26-letter alphabet?
2Hint 2Explore hintHide hint
Use a boolean[26] (or a hash set). For each character, lowercase it, and if it's a letter set seen[c - 'a'] = true.
3Hint 3Explore hintHide hint
At the end count how many slots are true. If the count is 26 it's a pangram; otherwise not. The array is fixed size, so the extra space is O(1).
A · Core idea
Make one pass over the sentence and, for every alphabetic character, mark its slot in a 26-element boolean array. After the pass, the sentence is a pangram exactly when all 26 slots are set.
The mental shift from the brute force. The naive version loops over letters and scans the string each time — 26 scans. Here we loop over the string once and let each character tell us which slot to flip. We collect presence information as we go, so by the end one pass has answered all 26 questions simultaneously.
Why a boolean array and not a counter. We never care whether e appeared three times or thirty — only that it appeared. A boolean[26] captures presence in one bit of meaning per letter; an int[26] frequency table would store more than we need. Less state means fewer ways to introduce a bug.
Why `c - 'a'` is the index. Subtracting the code of 'a' maps 'a' → 0, 'b' → 1, … 'z' → 25. That arithmetic is the whole reason a fixed array works in place of a hash set: the letter is its own index, so lookup and insert are plain O(1) array accesses with no hashing.
Why guarding 'is it a letter?' matters. Spaces and punctuation have codes outside a–z, so c - 'a' would land outside 0..25 and either corrupt memory (C-style) or throw (Java) or index wrongly. Skipping non-letters keeps every write inside the 26 valid slots.
Why counting to 26 is the final answer. A pangram is defined as 'all 26 present'. Once the pass finishes, summing the true slots and comparing to 26 is a direct restatement of the definition. You can even short-circuit and return early the instant the distinct count reaches 26.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "abca"Array state
i=0 'a'
set slot a → distinct 1
3 distinct ≠ 26 → not pangramC · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
A fixed, tiny alphabet means a hash set is overkill — the letter doubles as its own array index via c - 'a', giving O(1) lookups with zero hashing and O(1) space. Push it one step further and the whole presence array fits in a single 32-bit integer: bit k for letter k, then check the mask equals (1 << 26) - 1.
ComplexityO(n) time, O(1) space — One left-to-right pass touches each character once → O(n). The presence array has exactly 26 slots no matter how long the input is → O(1) auxiliary space.
E · Edge cases and pitfalls6s = "abcdefghijklmnopqrstuvwxyz" — All 26 letters once → distinct = 26 → pangram. Minimal-length pangram.Explore edge casesHide edge cases
Edge cases handled
s = "abcdefghijklmnopqrstuvwxyz"All 26 letters once → distinct = 26 → pangram. Minimal-length pangram.s = "aaaa bbbb"Only two distinct letters → distinct = 2 → not pangram, regardless of repeats.s = "The quick brown fox jumps over the lazy dog"Mixed case and spaces; folding case and skipping spaces still collects all 26 → pangram.
Pitfalls
- Indexing a space or punctuation into seen[c - 'a'] — out-of-range write/throw; guard with a letter check.
- Using an int[26] frequency table when a boolean[26] suffices — extra state, no benefit.
- Comparing the distinct count to 26 but forgetting to fold case, so capital-only letters are missed.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic String pangrams(String s) { boolean[] seen = new boolean[26]; int distinct = 0; for (int i = 0; i < s.length(); i++) { char c = Character.toLowerCase(s.charAt(i));Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Forgetting case normalisation
If you index the raw character, 'Q' maps to a slot outside a–z (or a different region), so the sentence's only 'q' never marks the lowercase slot and a valid pangram reads as 'not pangram'.
Explore the code comparisonHide the code comparison
Forgetting case normalisation
If you index the raw character, 'Q' maps to a slot outside a–z (or a different region), so the sentence's only 'q' never marks the lowercase slot and a valid pangram reads as 'not pangram'.
Don't write this
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= 'a' && c <= 'z') seen[c - 'a'] = true;
}Write this instead
for (int i = 0; i < s.length(); i++) {
char c = Character.toLowerCase(s.charAt(i));
if (c >= 'a' && c <= 'z') seen[c - 'a'] = true;
}2Indexing non-letters into the array
A space has code 32; 32 - 'a' is negative, so seen[c - 'a'] reads/writes out of bounds. Always confirm the character is a–z before computing the index.
Explore the code comparisonHide the code comparison
Indexing non-letters into the array
A space has code 32; 32 - 'a' is negative, so seen[c - 'a'] reads/writes out of bounds. Always confirm the character is a–z before computing the index.
Don't write this
for ch in s.lower():
seen[ord(ch) - ord('a')] = TrueWrite this instead
for ch in s.lower():
if 'a' <= ch <= 'z':
seen[ord(ch) - ord('a')] = True3Checking length instead of distinct presence
Comparing the sentence length to 26 (or counting total characters) confuses occurrences with distinct letters; 'aaaa…' of length 26 is not a pangram.
Explore the code comparisonHide the code comparison
Checking length instead of distinct presence
Comparing the sentence length to 26 (or counting total characters) confuses occurrences with distinct letters; 'aaaa…' of length 26 is not a pangram.
Don't write this
return "pangram" if len(s.replace(' ', '')) >= 26 else "not pangram"Write this instead
return "pangram" if len(set(c for c in s.lower() if c.isalpha())) == 26 else "not pangram"Revise
Carry the pattern into the interview
Pattern to remember
Fixed-alphabet presence set / 26-slot boolean
Code pattern to memorize
seen = boolean[26]
for ch in s.lower():
if 'a' <= ch <= 'z':
seen[ch - 'a'] = true
return count(seen) == 26Rules to remember
- 1Normalise case first —
Aandaare the same letter, so lowercase (or uppercase) before indexing. - 2Only the 26 letters count — ignore spaces, digits and punctuation, never index them into the array.
- 3You need presence, not frequency — a
boolean[26](or a set) is enough; don't waste a fullint[26]counter. - 4Answer is a single yes/no: did you collect all 26? Compare the distinct count to 26, or
ANDa full 26-bit mask. - 5Return early the instant the set hits size 26 if you want, but a single full pass is already O(n).
Use this pattern when
- You must confirm every symbol of a small fixed alphabet (a–z) appears at least once
- The universe of possible characters is tiny and known ahead of time
- You care about membership ('did it appear?'), not how many times
- A 26-slot array or a single 32-bit mask can stand in for a hash set
Choose another pattern when
- The alphabet is huge or unknown (full Unicode) → a real hash set, not a fixed array
- You need counts, not presence → use an int[] frequency table instead of a boolean[]
- You must find the *missing* letters, not just yes/no → still a set, but you report the gaps
- Case actually matters ('A' ≠ 'a') → skip the normalisation step
Interview takeaway
When the alphabet is small and fixed, a `boolean[26]` or a 26-bit integer mask replaces a hash set for free — O(1) space and cache-friendly. The same fixed-alphabet trick powers Valid Anagram, Unique Characters, and any 'have I seen every symbol?' check.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternFixed-alphabet presence tracking. When the universe of characters is small and known (26 letters), a boolean[26] or a 26-bit integer mask replaces a hash set: the character is its own index via c - 'a', giving O(1) lookups and O(1) space. The same trick underlies Valid Anagram, unique-character checks, and any 'have I seen every symbol?' question.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Check if the Sentence Is PangramComing soonIdentical core — return a boolean instead of the 'pangram' / 'not pangram' strings.
- Missing Letters in a PangramComing soonSame presence set, but report which of the 26 slots are still false.
- Valid AnagramFixed 26-slot table again — but count frequencies and compare two tables.
- First Unique Character in a StringComing soonFixed-alphabet frequency array, then a second pass for the first count-1 letter.