← Amazon Interview Insights

Amazon·Data Scientist·Technical Phone Screen·Senior

Senior
Jun 2026

Summary

Amazon Data Scientist technical screen, heavy on Python and NLP implementation. The questions were layered and kept building on each other, which I wasn't fully prepared for.

Questions Asked (4)

Q1

Given a 50GB UTF-8 text file on disk, how would you stream it in Python without loading it into memory, count case-insensitive word frequencies with Unicode normalization, and emit the top 10 words with counts and percentage of total tokens?

Algorithms & Data StructuresTechnical Trade-offsSystem Design
Author's notes

This is where I spent most of my mental energy.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying requirements and constraints, then outline a streaming pipeline using Python's file iteration and generators to process the file line-by-line. Emphasize Unicode normalization and case folding for accurate word counting, and discuss memory-efficient data structures like Counters or heaps for top-K extraction. Conclude with scalability considerations and trade-offs.

Pro tip: Mention that you would use `unicodedata.normalize('NFKC', text)` and `str.casefold()` for robust case-insensitive matching, and consider using `mmap` or chunked reading for I/O efficiency. Also, highlight that you would handle tokenization with a compiled regex to avoid loading the entire file.

1. Clarify Requirements and Constraints

Ask about file encoding, definition of a 'word', handling of punctuation, and whether the top 10 should be based on raw counts or normalized frequencies. Confirm that memory is limited and that the file cannot be loaded entirely.

2. Design Streaming Pipeline

Use a generator to read the file line-by-line or in chunks, apply Unicode normalization (NFKC) and case folding, then tokenize with a regex. Update a frequency dictionary incrementally.

3. Optimize Memory and Performance

Use `collections.Counter` for counting, but if memory is a concern, consider a two-pass approach or external sorting. For top-K, use a heap or `Counter.most_common(10)` after counting, but note that the latter loads all counts into memory.

4. Compute Percentages and Output

Track total token count during streaming. After processing, compute percentages for the top 10 words and format the output.

5. Discuss Trade-offs and Scalability

Mention alternative approaches like using Apache Spark or Dask for distributed processing, or using a database for external counting. Discuss time vs. memory trade-offs and potential bottlenecks.

Key Points to Mention

  • Use of `open()` with `encoding='utf-8'` and iterating over file object to stream lines.
  • Unicode normalization with `unicodedata.normalize('NFKC', text)` and case folding with `str.casefold()`.
  • Tokenization using `re.finditer` with a compiled pattern to extract words.
  • Memory-efficient counting with `collections.Counter` and top-K extraction using `heapq.nlargest`.
  • Handling of large files: chunked reading, generators, and avoiding `read()` or `readlines()`.
  • Trade-offs: exact counting vs. approximate algorithms (e.g., Count-Min Sketch) for extremely large data.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

What are the time and memory complexity of your streaming word count solution, and what practical optimizations would you apply?

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

Said O(n) time where n is total tokens, and O(V) memory for vocabulary size V.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clearly defining the streaming word count problem and the assumptions (e.g., single-pass, bounded memory). Then analyze time and memory complexity in terms of input size and vocabulary size, and finally discuss practical optimizations like approximate counting, partitioning, and efficient data structures.

Pro tip: Emphasize the trade-off between accuracy and resource usage, and mention how Amazon's scale might require distributed processing (e.g., using MapReduce or Flink) with considerations for skew and fault tolerance.

1. Clarify the problem and assumptions

Restate the streaming word count problem: process an unbounded stream of words and maintain counts. State assumptions like single pass, limited memory, and whether exact counts are required.

2. Analyze time complexity

Explain that each word is processed once, so time is O(N) where N is the number of words. Mention that hash map operations are O(1) on average, but worst-case could be O(V) if collisions, where V is vocabulary size.

3. Analyze memory complexity

Memory is O(V) where V is the number of distinct words. Discuss that V can be large and potentially unbounded, which is a challenge for streaming.

4. Discuss practical optimizations

Suggest optimizations: use approximate counting (e.g., Count-Min Sketch) to reduce memory, partition the stream by hash to handle large V, use efficient hash functions, and consider distributed processing for scale.

5. Address trade-offs and Amazon context

Highlight trade-offs between accuracy, memory, and latency. Relate to Amazon's scale: need for distributed systems, handling hot keys, and fault tolerance.

Key Points to Mention

  • Time complexity O(N) for N words, with hash map operations O(1) average.
  • Memory complexity O(V) for V distinct words, which can be large.
  • Approximate counting techniques like Count-Min Sketch or Lossy Counting to bound memory.
  • Partitioning the stream (e.g., by hash of word) to process in parallel or on disk.
  • Distributed processing frameworks (e.g., MapReduce, Flink) for scalability.
  • Trade-offs: exact vs approximate counts, memory vs accuracy, and latency considerations.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

Re-implement the word frequency counter using only plain Python dicts, without using collections.Counter.

Algorithms & Data Structures
Author's notes

Pretty straightforward.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the input format and expected output, then implement a function that iterates through the words, using a plain dict to accumulate counts. Emphasize that you are replicating Counter's core behavior—defaulting missing keys to 0—while discussing edge cases and efficiency.

Pro tip: Mention that you'd use dict.get(word, 0) + 1 or a defaultdict-like pattern with setdefault to avoid KeyError, and note that Counter is implemented in C for speed, so a pure-Python version may be slower—showing awareness of performance trade-offs.

1. Clarify requirements and edge cases

Ask about input format (list of words, string, file), case sensitivity, punctuation handling, and whether to return a dict or sorted results. Confirm that the goal is to mimic Counter's basic counting behavior.

2. Design the counting logic

Decide on a loop structure: iterate over each word, and for each word, increment its count in the dict. Use dict.get(word, 0) + 1 or setdefault to handle missing keys without exceptions.

3. Implement the function

Write clean, readable code with a function signature like count_words(words). Include a docstring and handle empty input gracefully by returning an empty dict.

4. Test and validate

Walk through a small example manually, then suggest unit tests for edge cases: empty list, single word, repeated words, and mixed case if relevant. Compare output with collections.Counter to ensure correctness.

5. Discuss performance and alternatives

Analyze time complexity (O(n)) and space complexity (O(k) for k unique words). Mention that while Counter is faster due to C implementation, the pure-Python version is acceptable for clarity and small-to-medium datasets.

Key Points to Mention

  • Use dict.get(word, 0) + 1 to avoid KeyError and simplify code.
  • Time complexity is O(n) where n is the number of words; space complexity is O(k) for k unique words.
  • Handle edge cases: empty input, non-string inputs, and case sensitivity if required.
  • Compare with collections.Counter: Counter provides additional methods like most_common, but core counting is the same.
  • Consider using setdefault or a try/except block as alternative patterns, but get is most concise.
  • Mention that Counter is implemented in C for performance, so pure-Python may be slower for large datasets.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

Implement safe_min and safe_max functions that handle NaN values, mixed comparable types via a key function, return a default when the iterable is empty, raise ValueError when no default is given, and are stable with equal keys.

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

This one surprised me more than it should have.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the requirements and edge cases, then outline a clean implementation using a sentinel object to track the best element and a key function for comparisons. Emphasize stability by only updating the best when the key is strictly better, and handle NaN by treating it as incomparable (e.g., using a custom comparison that returns False for NaN). Finally, discuss trade-offs such as time complexity and the choice of sentinel versus default parameter.

Pro tip: Mention that Python's built-in min/max already handle many of these cases, but implementing your own gives control over NaN and stability; also note that using a sentinel avoids issues with None as a valid element.

1. Clarify requirements and edge cases

Ask about expected behavior for NaN, mixed types, empty iterables, and stability. Confirm that key function is optional and defaults to identity.

2. Design the algorithm

Use a sentinel to track the best element and its key. Iterate through the iterable, compute key for each element, and compare using a custom comparison that handles NaN by treating it as never better.

3. Implement stability and NaN handling

Only update the best when the new key is strictly better (for min: new < best; for max: new > best). For NaN, ensure comparisons return False so NaN never replaces a non-NaN, and if all are NaN, return the first NaN.

4. Handle empty iterable and default

If no elements and default is provided, return default; otherwise raise ValueError. Use a sentinel to distinguish between no elements and an element that is None.

5. Analyze complexity and trade-offs

Discuss O(n) time and O(1) space. Mention that using key function adds overhead but enables mixed types. Compare with built-in min/max and explain why custom implementation is needed.

Key Points to Mention

  • Use of a sentinel object to track whether any element has been seen, avoiding ambiguity with None.
  • Stability: only replace the current best when the key is strictly better, preserving the first occurrence among equals.
  • NaN handling: treat NaN as incomparable, so it never becomes the result unless all elements are NaN.
  • Key function: apply it to each element for comparison, enabling mixed types and custom ordering.
  • Empty iterable: return default if provided, else raise ValueError.
  • Time and space complexity: O(n) time, O(1) extra space.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.