← Thumbtack Interview Insights

Thumbtack·Data Scientist·Technical Phone Screen·Senior

SeniorPrefer not to say
Jun 2026Remote

Summary

Thumbtack Data Scientist interview that was basically a full NLP engineering exercise disguised as a coding round. They wanted a TF-IDF implementation from scratch with sparse matrix output, complexity analysis, and unit tests, all without touching sklearn or NLTK. Pretty demanding for what I expected to be a lighter DS screen.

Questions Asked (5)

Q1

Implement TF-IDF from scratch using only Python, NumPy, and SciPy. Your implementation should include a memory-efficient tokenizer with min_df/max_df filtering, smoothed IDF computation, and output a CSR sparse matrix with vocabulary ordered lexicographically.

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

This was the core of the whole interview and it took basically the full session.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying requirements and constraints (e.g., dataset size, memory limits) to guide design choices. Then walk through the pipeline: tokenization with min_df/max_df filtering, smoothed IDF computation, and CSR matrix construction with lexicographically ordered vocabulary. Emphasize memory efficiency and use of NumPy/SciPy for sparse operations.

Pro tip: Mention that you would use a two-pass approach: first pass to count document frequencies and build vocabulary, second pass to compute TF-IDF values and populate the sparse matrix. This avoids storing the entire tokenized corpus in memory.

1. Clarify requirements and constraints

Ask about dataset size, memory limits, and whether the implementation needs to handle streaming data. This informs design decisions like using a two-pass approach and sparse data structures.

2. Design memory-efficient tokenizer with min_df/max_df

Implement a tokenizer that processes documents one at a time, using a dictionary to count document frequencies. Apply min_df and max_df thresholds to filter tokens, and build a vocabulary sorted lexicographically.

3. Compute smoothed IDF

Calculate IDF as log((1 + n) / (1 + df)) + 1, where n is the total number of documents and df is the document frequency of a term. This smoothing prevents zero division and downweights very frequent terms.

4. Construct CSR sparse matrix

Iterate through documents again, compute TF-IDF values for each term, and populate the CSR matrix using arrays for data, indices, and indptr. Ensure the vocabulary is ordered lexicographically so that column indices correspond to sorted terms.

5. Validate and discuss trade-offs

Test the implementation on a small dataset, compare with scikit-learn's TfidfVectorizer, and discuss trade-offs like memory vs. speed, and the impact of min_df/max_df on feature dimensionality.

Key Points to Mention

  • Two-pass approach: first pass for document frequency and vocabulary, second pass for TF-IDF computation to save memory.
  • Use of Python's collections.Counter or defaultdict for efficient counting during tokenization.
  • Smoothed IDF formula: log((1 + n) / (1 + df)) + 1 to avoid division by zero and handle unseen terms.
  • CSR matrix construction using scipy.sparse.csr_matrix with precomputed data, indices, and indptr arrays.
  • Lexicographic ordering of vocabulary to ensure consistent column indices and reproducibility.
  • Memory efficiency considerations: processing documents in a streaming fashion, using generators, and avoiding dense matrices.

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

Q2

How does your implementation handle out-of-vocabulary tokens at transform time, and how do you support L2 normalization per document row?

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

Straightforward once I'd built the main structure.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the context: whether you're using a pre-trained model or a custom pipeline, and what the downstream task is. Then explain your OOV strategy (e.g., subword tokenization, hashing, or an <UNK> token) and how you ensure L2 normalization per row, such as using scikit-learn's Normalizer or manual row-wise division by the L2 norm. Emphasize trade-offs like information loss vs. robustness and computational efficiency.

Pro tip: Mention that you validate the normalization by checking that each row's L2 norm equals 1 (within floating-point tolerance) and that you handle zero-norm rows gracefully to avoid division by zero. This shows attention to edge cases and production readiness.

1. Clarify the pipeline and requirements

Briefly describe the overall ML pipeline (e.g., text vectorization, model inference) and the specific transform step. Confirm whether the question refers to a pre-trained model's tokenizer or a custom vectorizer, and what the expected input/output format is.

2. Explain OOV handling strategy

Detail how you handle tokens not seen during training: options include mapping to a special <UNK> token, using subword tokenization (e.g., BPE, WordPiece), feature hashing, or character n-grams. Discuss why you chose that approach and its impact on model performance.

3. Describe L2 normalization per row

Explain that L2 normalization scales each document vector to unit norm. Mention implementation details: using scikit-learn's Normalizer(norm='l2') in a pipeline, or manually computing row norms and dividing. Highlight that normalization is applied after vectorization and before downstream tasks.

4. Address edge cases and validation

Discuss handling zero-norm rows (e.g., empty documents) by leaving them as zeros or adding a small epsilon. Explain how you validate normalization, such as asserting that row norms are close to 1 and monitoring for NaNs.

5. Summarize trade-offs and production considerations

Conclude with trade-offs: OOV strategies affect vocabulary size and generalization; L2 normalization impacts similarity computations and model convergence. Mention scalability (e.g., sparse matrices) and integration with serving infrastructure.

Key Points to Mention

  • Subword tokenization (BPE, WordPiece) as a robust OOV solution
  • Use of <UNK> token and its limitations (information loss)
  • Feature hashing for high-cardinality or streaming scenarios
  • L2 normalization formula: divide each row by its L2 norm
  • Implementation via scikit-learn's Normalizer or manual NumPy operations
  • Handling zero-norm rows to avoid division by zero
  • Validation of normalization (row norms ≈ 1)
  • Trade-offs between OOV strategies and model performance

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

Q3

Add an inverse_transform method that reconstructs the top-k terms for a given document by TF-IDF score.

Algorithms & Data StructuresData Modeling
Author's notes

I liked this part actually.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

First, clarify the expected input and output: the method should take a document vector (or index) and return the top-k terms with highest TF-IDF scores. Then, outline the algorithm: retrieve the document's TF-IDF vector, sort terms by score, and return the top-k. Finally, discuss implementation details like handling ties, efficiency, and integration with the existing vectorizer.

Pro tip: Emphasize that inverse_transform should be the exact inverse of transform, so it must use the same vocabulary and IDF weights. Also, mention that for large vocabularies, using a heap or argpartition is more efficient than full sorting.

1. Clarify requirements

Confirm the input format (e.g., document index or sparse vector) and output format (e.g., list of (term, score) tuples). Ask about handling ties and whether to return scores or just terms.

2. Retrieve document vector

If given a document index, access the corresponding row from the TF-IDF matrix. If given a vector, use it directly. Ensure it's in the same feature space as the vectorizer.

3. Sort terms by score

Extract non-zero entries (term indices and scores) and sort them in descending order. For efficiency with large vocabularies, use a heap or np.argpartition to get top-k without full sort.

4. Map indices to terms

Use the vectorizer's vocabulary (e.g., get_feature_names_out) to convert term indices to actual terms. Return the top-k terms with their scores.

5. Handle edge cases

Consider cases like k larger than number of non-zero terms, empty documents, and ties. Discuss whether to return fewer than k terms if necessary.

Key Points to Mention

  • Sparsity: TF-IDF matrices are sparse, so only consider non-zero entries.
  • Efficiency: Use argpartition or heap for O(n) top-k selection instead of O(n log n) sorting.
  • Consistency: Ensure the method uses the same vocabulary and IDF weights as the forward transform.
  • Tie-breaking: Decide on a deterministic tie-breaking rule (e.g., alphabetical order or first occurrence).
  • Integration: The method should be part of the vectorizer class, leveraging its attributes like vocabulary_ and idf_.
  • Output format: Return terms and optionally their scores, possibly as a list of tuples or a dictionary.

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

Q4

Analyze the time and space complexity of your fit and transform methods.

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

I went through this verbally while coding which probably wasn't the smoothest delivery.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clearly defining what your fit and transform methods do and the data structures they operate on. Then, systematically derive the time and space complexity for each method, considering best, average, and worst cases, and explain how they interact in a typical pipeline. Finally, discuss any trade-offs and optimizations you made or would consider.

Pro tip: Always relate the complexity to the practical impact on model training and inference time, and mention how you would optimize if the data scales significantly. This shows you think beyond theoretical analysis to real-world performance.

1. Describe the methods and data

Briefly explain what fit and transform do in your implementation, including input data shape, types, and any assumptions (e.g., sparse vs dense).

2. Analyze fit complexity

Derive the time and space complexity of fit, breaking down each operation (e.g., sorting, matrix multiplication) and considering the number of samples and features.

3. Analyze transform complexity

Similarly, derive the time and space complexity of transform, noting any dependencies on parameters learned during fit.

4. Discuss trade-offs and edge cases

Mention best/worst-case scenarios, how complexity changes with data characteristics (e.g., sparsity), and any trade-offs between time and space.

5. Relate to practical impact and optimizations

Explain how these complexities affect real-world usage and what optimizations you would apply if data scales (e.g., vectorization, incremental learning).

Key Points to Mention

  • Big-O notation for time and space, with clear variables (n samples, m features).
  • Distinction between fit and transform complexities and how they combine in a pipeline.
  • Impact of data structures (e.g., sparse matrices, arrays) on complexity.
  • Best, average, and worst-case scenarios, especially for algorithms like sorting or matrix operations.
  • Trade-offs between time and space, such as caching vs recomputation.
  • Practical implications for scalability and potential optimizations (e.g., using NumPy vectorization, chunking).

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

Q5

Write a unit test demonstrating correctness of your TF-IDF implementation on a small 3-document corpus that includes repeated terms.

Algorithms & Data StructuresData Modeling
Author's notes

I wrote something basic with a corpus like ['the cat sat', 'the cat', 'the dog sat'] and checked that 'the' gets a low IDF score relative to 'dog', and that the output matrix shape matches.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by defining a tiny 3-document corpus with deliberate term repetition, then manually compute the expected TF-IDF values for a few key terms. Write a unit test that asserts your implementation's output matches these hand-calculated values, covering both term frequency and inverse document frequency components.

Pro tip: Use a corpus where one term appears in all documents (IDF = 0) and another appears in only one document (high IDF) to test edge cases and ensure your IDF smoothing or default behavior is correct.

1. Design a minimal corpus with controlled repetition

Create 3 short documents where you can easily track term frequencies and document frequencies. Include a term that repeats within a document and a term that appears across multiple documents.

2. Manually compute expected TF-IDF scores

For selected terms, calculate TF (raw count or normalized) and IDF (using your chosen formula, e.g., log(N/df) or smoothed) by hand. Document these expected values in comments or as test fixtures.

3. Write the unit test with clear assertions

Instantiate your TF-IDF vectorizer, fit it on the corpus, and transform the documents. Assert that the output matrix matches the expected values within a small tolerance (e.g., 1e-6) for the chosen terms.

4. Test edge cases and invariants

Include checks for terms with zero IDF (appearing in all docs) and terms absent from the vocabulary. Optionally, verify that the sum of TF-IDF weights for a document is positive or that L2 normalization is applied if applicable.

5. Explain the test's purpose and coverage

Briefly state why this test demonstrates correctness: it validates both term frequency and inverse document frequency calculations, handles repetition, and catches common implementation errors like incorrect smoothing or normalization.

Key Points to Mention

  • Choice of TF weighting (raw count, log normalization, double normalization) and its impact on expected values.
  • IDF formula: whether using log(N/df), log((1+N)/(1+df))+1, or other smoothing, and why it matters for terms in all documents.
  • Handling of out-of-vocabulary terms and whether they are ignored or assigned zero weights.
  • Normalization (e.g., L2) and its effect on the final TF-IDF vectors, which must be accounted for in assertions.
  • Use of a tolerance in floating-point comparisons to avoid brittle tests due to numerical precision.
  • The importance of testing with a small, manually verifiable corpus to build confidence before scaling to larger datasets.

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