</> MAANG.io
coding interview · 201

Intermediate

Advanced coding interview preparation covering complex algorithms, system design, and optimization techniques for senior-level positions.

0/170 solved 0% complete

Streaming

What is this?

Characters arrive one at a time, forever, and after each one you must answer: does any dictionary word end right here? That is a question about suffixes of everything seen so far — and a trie is built for prefixes. Re-scanning the recent history against every word on every character is far too slow.

The fix is to change the data rather than the structure. Store the words backwards. Now walking the recent characters in reverse order through the trie answers the suffix question with an ordinary prefix walk.

flowchart TD A["dictionary: cd, f, kl"] --> B["insert REVERSED: dc, f, lk"] C["stream ... a b c d"] --> D["keep the recent characters"] D --> E["on each arrival, walk BACKWARDS
through the reversed trie"] E --> F{"hit an is_end node?"} F -->|yes| G["a word ends here → true"] F -->|"no child"| H["stop early — nothing can match"]

💡 Fun fact: the walk is bounded by the longest dictionary word, not by the length of the stream. As soon as the reversed walk reaches a node with no matching child, no longer suffix can match either — because every longer candidate would have to pass through that node. So a stream of a billion characters still costs at most L steps per arrival, and the history buffer can be truncated to L characters and forgotten beyond that.

🔓 The 1 problem in this chapter is free. Sign in with Google or Microsoft to start solving.


The one-line idea: a trie answers prefix questions, and reversing the dictionary turns "does a word end here?" into exactly that. The walk terminates the moment no child matches, so the cost is bounded by the longest word rather than the stream.


1. The implementation

class StreamChecker:
    def __init__(self, words):
        self.root = Node()
        self.max_len = max(map(len, words))
        for w in words:
            node = self.root
            for ch in reversed(w):                  # store backwards
                node = node.children.setdefault(ch, Node())
            node.is_end = True
        self.history = deque()

    def query(self, letter):
        self.history.appendleft(letter)             # newest first
        if len(self.history) > self.max_len:
            self.history.pop()                      # forget what can never matter
        node = self.root
        for ch in self.history:                     # newest → oldest
            if ch not in node.children:
                return False                        # no longer suffix can match
            node = node.children[ch]
            if node.is_end:
                return True
        return False

Two decisions carry the whole solution. appendleft keeps the newest character at the front, so iterating the deque naturally walks backwards through the stream. And the early return on a missing child is what bounds the work — it is not an optimisation, it is the correctness argument for why the cost does not grow with the stream.

Capping the history at max_len is the memory counterpart: characters older than the longest word can never participate in a match.


2. A 30-second worked example

Dictionary ["cd", "f", "kl"], stored reversed as dc, f, lk. Stream: a, b, c, d.

query 'a'  history = [a]
           root has no 'a' child → False

query 'b'  history = [b, a]
           root has no 'b' child → False

query 'c'  history = [c, b, a]
           root has no 'c' child → False        ← 'c' only appears INSIDE "dc"

query 'd'  history = [d, c, b, a]
           root → 'd' ✓ (not is_end)
                → 'c' ✓ is_end → True          ← "cd" ends here ✅

The third query is the instructive one: c alone is not a word, and the reversed trie's root has no c edge because cd reversed starts with d. Only when the d arrives does the backwards walk d → c spell a stored word.


3. Where you'll actually meet this

  • Content moderation. Matching an inbound message stream against a blocklist without re-scanning history.
  • Intrusion detection. Signature matching over network traffic as packets arrive.
  • Log monitoring. Triggering when a known error pattern completes in a live log tail.
  • Voice and text input. Detecting wake words or command phrases in a continuous stream.
  • Protocol parsing. Recognising terminator sequences in a byte stream, where you never get to look ahead.

4. Problems in this chapter

▶ Stream of Characters

Report, after each arriving character, whether any dictionary word ends there. Reversed trie, bounded history, backwards walk.
Pattern: reversed trie over a stream. Target: O(L) per query, O(L) memory for the history.


5. Common pitfalls 🚫

  • Storing the words forwards. Then each query needs every possible start position — quadratic and unnecessary.
  • Appending to the end of the history and iterating forwards. The walk must go newest to oldest; appendleft makes that natural.
  • Never trimming the history. Memory grows without bound for no benefit; anything older than the longest word is dead.
  • Continuing after a missing child. The early exit is the reason the cost is bounded — removing it does not just slow things down, it obscures why the solution is correct.
  • Checking is_end only at the end of the walk. A match can occur at any depth, so test after every step.
  • Assuming the alphabet is lowercase-only without saying so; a dictionary per node keeps it general.
  • Rebuilding the trie per query. It is built once in the constructor.

6. Key takeaways

  1. Reverse the data to reverse the question. Suffix matching becomes prefix matching with no change to the structure.
  2. Keep the newest character first so the natural iteration order is the backwards walk.
  3. Stop at the first missing child — that bound is what makes the solution scale.
  4. Trim the history to the longest word. Older characters can never matter.
  5. Test is_end at every depth, not only at the end.
  6. Cost is bounded by the dictionary, not the stream — the sentence to say out loud.
  7. Why interviewers like it: the naive solution is obvious and quadratic, and the fix is a single reframing rather than a new data structure, which makes it a clean test of insight over recall.

Order: Stream of Characters.

Stream of Characters

Core idea: A word matches as a suffix of the stream — it ends now, at the newest character. Reading a suffix from its end is reading the reversed word from its start. So store every dictionary word reversed in a trie, and on each new character walk that trie starting from the newest character and going backward through the recent stream. If the backward walk ever lands on an is_end node, the corresponding word just finished. No re-scanning the whole stream, no checking every word — one trie walk bounded by the longest word.

The problem, in plain words

Implement a StreamChecker (LeetCode 1032). It's built once with a dictionary of words, then fed characters one at a time, forever:

  • StreamChecker(words) — constructor, initialise the structure with the given words.
  • query(letter) — return True if and only if for some k >= 1, the last k characters queried (oldest-to-newest, including this one) spell one of the dictionary words. Otherwise False.

The key word is suffix: a word counts only if it ends exactly at the character you just queried. cd matches the moment you query d, but only if c was the character right before it.

Here's the canonical sequence — dictionary ["cd", "f", "kl"] — showing the answer flip as characters stream in:

Step query(c) Stream so far Result Why
1 a a False no word ends here
2 b ab False
3 c abc False c alone isn't a word
4 d abcd True …cdcd is in the list
5 e abcde False
6 f abcdef True …ff is in the list
7 g …fg False
8–11 h i j k …hijk False k alone isn't a word
12 l …kl True …klkl is in the list

The "stream so far" column is unbounded, but as we'll see you never need to keep more than the longest word's worth of recent characters.

Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.