Everything Said Twice
An easy Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- easy
- Seniority
- L3
- Asked in
- Tata Consultancy Services
Problem
Given a string of words separated by whitespace, return a dict mapping each distinct word to the number of times it appears.
Every word leaves a mark. Count who keeps coming back.
Example 1
Input:
text = "click view click submit view view"
Output:
{"view":3,"click":2,"submit":1}Example 2
Input:
text = "hello"
Output:
{"hello":1}Worked solution and explanation
What this is really testing
Strip the costume and this is a tokenize-and-tally: chop a string into words, count how many times each shows up. Everyone gets the counting part right; almost nobody trips on the dict accumulation. The thing that actually separates people is one keystroke: whether you call text.split() with no argument or text.split(' ') with a literal space. They look identical on the happy path 'a b c' and diverge the instant the input has a double space, a tab, or a leading blank. Pick the wrong one and you silently seed your dict with an empty-string key that carries a real count, and your output is wrong in a way that passes casual eyeballing.
How you get there
Step 1: Split on whitespace, not on a space character
The spec says 'separated by whitespace' and that phrasing is the tell. No-arg split() collapses runs of spaces, tabs, and newlines, and strips leading/trailing whitespace, so you never manufacture empty tokens. The moment you hardcode split(' ') you are counting on the input being perfectly single-spaced, which real text never is.
Step 2: Tally in one pass
Feed the token list straight into collections.Counter. It walks the tokens once, O(n), and increments in C. You could hand-roll d[w] = d.get(w, 0) + 1 and it would be correct, but Counter is the idiom a reviewer expects here and it comes with .most_common() for the inevitable follow-up.
Step 3: Hand back a plain dict
The signature promises -> dict. Counter IS a dict subclass and compares equal, so tests usually pass either way, but wrapping in dict() matches the contract exactly and won't surprise a caller that inspects the concrete type. Empty input needs no special case: ''.split() is [], Counter([]) is empty, dict() is {}.
from collections import Counter
def word_counts(text: str) -> dict:
return dict(Counter(text.split()))One pass to split, one pass to count.
- split(' ') — the trap
- ' the the cat '.split(' ') -> ['', '', 'the', '', 'the', 'cat', ''] . Counter now has a '' key with count 3 and 'the' count 2. The output silently carries a junk key and passes a quick glance.
- split() — correct
- ' the the cat '.split() -> ['the', 'the', 'cat'] . Counter is {'the': 2, 'cat': 1} . Runs collapse, ends trim, no empty tokens, ever.
Common follow-up questions
- What changes if words should be counted case-insensitively? (Counter(text.lower().split()) — but note .lower() allocates a full copy, O(n) extra memory, and check whether the spec actually wants case folded.)
- How would you return the top 3 most common words? (Counter(...).most_common(3) gives (word, count) tuples in descending order; be ready to discuss that ties break on insertion order.)
- How would you handle a 10 GB file that doesn't fit in memory? (Stream line by line, split each line, Counter.update(tokens) in place; the Counter stays bounded by the vocabulary size, not the file size.)