Google Senior Data Engineer Interview Preparation Guide
Google's Data Engineer interview process for Senior level candidates consists of a recruiter screening call followed by a technical phone screen and 4-5 onsite interview rounds. Each round is 45-60 minutes and evaluates different competencies including system design, SQL proficiency, coding ability, and cultural alignment. The process emphasizes real-world problem-solving, scalability thinking, and hands-on technical expertise with Google Cloud Platform services.
Interview Rounds
Recruiter Screening
What to Expect
Initial 30-minute call with a Google recruiter to assess your background, experience level, and basic understanding of data engineering. The recruiter will verify your interest in the role, discuss your compensation expectations, and ensure you meet the minimum requirements for a Senior Data Engineer position. This is also your opportunity to learn more about the team and role specifics.
Tips & Advice
Be concise and specific about your data engineering experience. Highlight projects where you designed or optimized large-scale data systems. Mention your experience with cloud platforms and big data technologies. Ask thoughtful questions about the team's data infrastructure and challenges they face. Show genuine interest in Google's data ecosystem. Have your resume readily available and be prepared to walk through key projects briefly. Be honest about your experience level—for Senior roles, Google expects 5+ years of hands-on data engineering experience.
Focus Topics
Leadership and Mentorship Experience
Discuss any experience leading data engineering projects, mentoring junior engineers, or collaborating with cross-functional teams. For Senior roles, some leadership component is expected.
Practice Interview
Study Questions
Understanding of Google's Data Infrastructure Needs
Show that you understand Google's scale—billions of users, petabytes of data, and the infrastructure required to support that. Mention specific Google products or services that process vast amounts of data (YouTube, Search, Google Analytics).
Practice Interview
Study Questions
Familiarity with Google Cloud Platform Services
Demonstrate awareness of Google's data platform including BigQuery, Dataflow, Pub/Sub, Cloud Storage, and Dataproc. Share any hands-on experience you have with GCP or discuss how you've used equivalent services on other cloud platforms.
Practice Interview
Study Questions
Professional Background and Data Engineering Experience
Clearly articulate your career progression as a data engineer, highlighting the scale and complexity of systems you've worked with. Emphasize experience with building and maintaining data pipelines, designing data warehouses, and working with big data technologies.
Practice Interview
Study Questions
Technical Phone Screen
What to Expect
A 45-60 minute technical interview conducted via phone or video focusing on your ability to solve real-world data engineering problems. You'll be asked to work through data infrastructure design questions, discuss database optimization, solve SQL/coding problems, and explain your approach to building scalable systems. The interviewer is assessing your technical depth, problem-solving methodology, and ability to handle ambiguous requirements.
Tips & Advice
Think out loud and explain your reasoning as you solve problems. Start with clarifying questions to understand the scope and requirements before diving into solutions. For data pipeline questions, discuss extraction methods, transformation logic, and storage strategies. Consider scalability, fault tolerance, and cost from the start. Use specific GCP terminology and services when relevant. Don't rush to code—focus on the architecture and design first. Be prepared to discuss trade-offs between different approaches. If you don't know something, be honest but show how you would approach learning it. Practice solving data problems under time constraints.
Focus Topics
Data Structures and Algorithm Problem-Solving
Solve coding problems related to data processing, data structures, and algorithms. Problems may include stream processing, data aggregation, or optimization challenges specific to data engineering contexts.
Practice Interview
Study Questions
Real-World Data Problems and Trade-offs
Discuss handling of data quality issues, missing data, schema evolution, and data consistency. Address cost optimization, performance vs reliability trade-offs, and practical solutions to infrastructure challenges.
Practice Interview
Study Questions
Big Data Technologies and Distributed Systems Concepts
Explain how MapReduce, Spark, Hadoop, and other distributed computing frameworks work. Discuss consistency models, fault tolerance, data replication, and system design principles for distributed data processing.
Practice Interview
Study Questions
Database Management and Query Optimization
Demonstrate expertise in database design, indexing strategies, query optimization, and performance tuning. Discuss handling of large datasets and schema design for specific use cases. Include knowledge of partitioning, clustering, and materialized views in BigQuery.
Practice Interview
Study Questions
Large-Scale Data Pipeline Design and Optimization
Design and optimize ETL pipelines that handle massive data volumes. Address data ingestion strategies, transformation logic, error handling, and scalability considerations. Discuss real-time vs batch processing trade-offs and when to use each approach.
Practice Interview
Study Questions
Onsite Round 1: Data Architecture and System Design
What to Expect
This 45-60 minute round focuses on your ability to design large-scale data systems and architectures. You'll be presented with a complex real-world scenario (e.g., design YouTube's video processing pipeline, or build a real-time data warehouse for Google Analytics) and asked to architect a complete solution. The interviewer assesses your understanding of scalability, reliability, cost optimization, and your ability to make sound architectural decisions. You'll be expected to consider multiple approaches and explain trade-offs.
Tips & Advice
Start by asking clarifying questions about scale, requirements, latency, throughput, and consistency needs. Never make assumptions about what 'large-scale' means without clarifying. Draw diagrams showing data flow, system components, and interactions. Discuss which Google Cloud services (BigQuery, Dataflow, Pub/Sub, Cloud Storage, etc.) fit different parts of your architecture and why. Address scalability bottlenecks and explain how your design handles them. Consider both batch and streaming requirements if applicable. Discuss failure scenarios and recovery strategies. Talk about cost implications and optimization opportunities. For a Senior level, you're expected to own the end-to-end design and articulate complex trade-offs confidently.
Focus Topics
Cost Optimization and Resource Management
Design data systems with cost efficiency in mind. Discuss strategies like caching, materialized views, data partitioning, compression, and appropriate service choices. Balance performance requirements with budget constraints.
Practice Interview
Study Questions
Google Cloud Platform Service Selection and Integration
Demonstrate knowledge of when and how to use BigQuery, Dataflow, Pub/Sub, Cloud Storage, Dataproc, Cloud Composer, and other GCP data services. Explain why specific services are chosen for different components of the architecture.
Practice Interview
Study Questions
Fault Tolerance and Data Reliability
Design systems with built-in fault tolerance, redundancy, and recovery mechanisms. Discuss replication strategies, backup approaches, and disaster recovery for critical data systems. Address consistency guarantees and failure scenarios.
Practice Interview
Study Questions
Scalability and Performance Optimization in Data Systems
Design systems that scale horizontally and vertically. Discuss how to handle increasing data volume, query concurrency, and user growth. Address bottlenecks like storage, compute, networking, and I/O. Explain optimization techniques specific to data systems.
Practice Interview
Study Questions
End-to-End Data Architecture Design
Design complete data systems from data source to analytics consumption. Include data ingestion, transformation, storage, and serving layers. Consider schema design, data partitioning strategies, and appropriate technology choices at each layer.
Practice Interview
Study Questions
Onsite Round 2: SQL and Data Analysis
What to Expect
A 45-60 minute technical round focused on SQL expertise and data analysis. You'll be given real-world data scenarios requiring complex SQL queries, often involving window functions, subqueries, CTEs, joins, and aggregations. Questions may require you to write efficient queries on large datasets, optimize existing queries, or analyze data to answer business questions. You may also discuss BigQuery-specific optimizations and best practices.
Tips & Advice
Write clean, readable SQL that follows best practices. Always explain your approach before writing code. Consider performance implications of your queries. Use appropriate indexing strategies and query optimization techniques. For BigQuery specifically, avoid SELECT * and specify only required columns, use partitioning and clustering effectively, and understand cost implications (BigQuery charges per bytes scanned). Discuss materialized views and caching when relevant. Be prepared to optimize a slow query by analyzing its execution plan. Test your logic mentally or on paper before presenting. For Senior level, you should be able to write complex queries involving multiple joins, window functions, and subqueries efficiently. Consider data types, null handling, and edge cases.
Focus Topics
Handling Complex Data Scenarios and Edge Cases
Address data quality issues, null handling, data type conversions, schema evolution, and complex analytical requirements. Deal with scenarios like slowly changing dimensions, data anomalies, and multi-stage data transformations.
Practice Interview
Study Questions
Data Modeling for Analytics and Reporting
Design data models that support efficient analytics queries. Understand star schema, snowflake schema, denormalization trade-offs, and dimensional modeling. Create schemas that balance query performance with storage efficiency.
Practice Interview
Study Questions
Complex SQL Query Writing and Optimization
Write efficient SQL for complex data analysis problems. Master window functions, CTEs (Common Table Expressions), subqueries, multiple joins, and aggregations. Optimize queries for performance considering indexing, query execution plans, and resource usage.
Practice Interview
Study Questions
BigQuery-Specific Query Optimization Techniques
Apply BigQuery-specific optimization strategies including column pruning, partitioning, clustering, materialized views, caching, and appropriate data types. Understand BigQuery's pricing model and cost implications of query design choices.
Practice Interview
Study Questions
Onsite Round 3: Coding and Problem-Solving
What to Expect
A 45-60 minute technical coding round focused on data structures, algorithms, and problem-solving ability. You may receive coding problems in your language of choice (Python, Java, C++, Go) that test your understanding of data structures, algorithmic thinking, and code quality. Problems may be general software engineering problems or specific to data processing scenarios. The focus is on your problem-solving approach, code clarity, and ability to optimize solutions.
Tips & Advice
Choose a language you're comfortable with—most data engineers use Python at Google. Start by clarifying the problem and discussing your approach before coding. Break down the problem into manageable pieces. Write clean, readable code with meaningful variable names and comments where necessary. Consider time and space complexity of your solution. Think about edge cases and test your logic before presenting. Be prepared to optimize your solution and discuss trade-offs. For data engineering specific problems, think about how your solution scales to large datasets. Don't over-engineer but show awareness of production considerations like error handling. At Senior level, demonstrate not just that you can solve the problem, but that you can solve it efficiently and elegantly.
Focus Topics
Problem-Solving Methodology and Communication
Demonstrate clear thinking when approaching unfamiliar problems. Ask clarifying questions, consider multiple approaches, and explain your reasoning. Communicate your thought process throughout the problem-solving, not just at the end.
Practice Interview
Study Questions
Code Quality and Optimization
Write production-quality code that is readable, maintainable, and efficient. Optimize solutions for performance. Consider edge cases, error handling, and scalability. Demonstrate understanding of trade-offs between code simplicity and performance.
Practice Interview
Study Questions
Data Processing and Stream Processing Algorithms
Solve problems related to data processing at scale including streaming data, aggregations, windowing, and distributed processing patterns. Address scenarios like counting unique elements, finding patterns in streams, or processing events in order.
Practice Interview
Study Questions
Data Structures and Algorithm Design
Solve problems using appropriate data structures (arrays, linked lists, hash tables, trees, heaps, graphs). Understand time and space complexity trade-offs. Apply algorithmic techniques like sorting, searching, dynamic programming, and graph algorithms. Master these fundamentals for both general and data-specific problems.
Practice Interview
Study Questions
Onsite Round 4: Behavioral and Cultural Alignment
What to Expect
A 45-60 minute behavioral interview assessing your past experience, leadership qualities, collaboration skills, and alignment with Google's culture and values. You'll be asked about specific projects you've led, how you've handled challenges, your approach to mentoring and cross-team collaboration, and situations where you demonstrated core Google values like innovation, user focus, and integrity. This round also allows you to ask questions about the team and role.
Tips & Advice
Prepare specific stories from your career that demonstrate leadership, impact, and learning. Use the STAR method (Situation, Task, Action, Result) to structure your responses. Focus on projects where you owned significant responsibility, solved complex problems, or mentored others. Demonstrate how you handle ambiguity, disagree respectfully, and drive results. Show genuine interest in Google's mission and products. Discuss how your engineering approach aligns with scalability, reliability, and user impact. Be authentic—Google values diversity of thought but also cultural fit around core values. Ask thoughtful questions about the team's challenges, culture, and how success is measured. For Senior level, emphasize your impact on team growth, architectural decisions, and how you've influenced engineering practices. Show that you think beyond just coding to system-level improvements.
Focus Topics
Alignment with Google Values and Impact Thinking
Connect your work to Google's mission of organizing information and making it accessible. Discuss how you think about user impact, scale, and quality. Demonstrate your commitment to innovation, integrity, and continuous improvement.
Practice Interview
Study Questions
Handling Ambiguity and Technical Challenges
Discuss situations where requirements were unclear, technical problems were complex, or you had to make trade-offs with limited information. Explain your problem-solving approach and how you reached decisions. Show your resilience and learning from failures.
Practice Interview
Study Questions
Mentorship and Team Development
Share experiences mentoring junior engineers or other team members. Discuss how you helped others grow, specific technical guidance you provided, and the outcomes of your mentorship. Show your commitment to developing others.
Practice Interview
Study Questions
Cross-Functional Collaboration and Communication
Describe successful collaborations with data scientists, product managers, and other engineers. Explain how you communicated technical concepts to non-technical stakeholders. Share examples of resolving technical disagreements or aligning teams around a solution.
Practice Interview
Study Questions
Leadership of Complex Data Engineering Projects
Discuss projects where you owned end-to-end data systems or significant components. Describe your role in architecture decisions, how you managed complexity, and the impact of your work. Highlight projects involving scalability challenges, cross-team coordination, or technical innovation.
Practice Interview
Study Questions
Frequently Asked Data Engineer Interview Questions
Explain a coaching framework you use, like the GROW model or Socratic questioning, and walk through how you'd apply it in a real one-on-one with someone who wants to grow a specific skill.
Sample Answer
Direct answer
GROW is a four-stage, question-led coaching structure: Goal (what success looks like), Reality (the current state), Options (possible paths forward), and Way forward (specific commitments). Applied to a 1:1 with someone who wants to grow a specific skill, it turns a vague aspiration into a concrete next step, and the same question-led habit also works inside a work review, not only a scheduled conversation.
Walking through the four stages
- Goal. Get specific: "What would 'better at this' actually look like, concretely, and how would you know it happened?"
- Reality. Surface the current state without judgment: "Tell me about a recent situation where this was hard, what made it hard?"
- Options. Generate paths rather than prescribing one: "What could you try next, and who or what could help?"
- Way forward. Get a specific, small commitment: "Which one thing will you actually do before we talk again, and what support do you need from me?"
Socratic questioning is the companion technique that runs through all four stages: instead of stating the answer, ask a question that leads the person to notice the gap themselves ("what did you expect to happen there, versus what actually happened?"). It works well when there's time to let someone arrive at the insight; it works poorly when someone is genuinely blocked and just needs the direct answer.
Extending this into reviewing someone's work
The same question-led approach makes a review of someone's work (code, a document, a design, an analysis) constructive rather than purely corrective. Concrete techniques: a review template that separates "must fix" from "worth considering" from "just for your awareness," so feedback doesn't read as one undifferentiated pile of criticism; annotated examples that show a better version alongside the original with a short reason, not just a comment naming the problem; and a Socratic question left in the review itself ("what happens here if this is empty?") instead of stating the bug outright, when the goal is teaching and there's no urgency forcing a direct fix.
Worked example
In a 1:1, a mentee said they wanted to get better at making structural decisions independently instead of always checking first. Goal: they described what "independent" would look like in practice (making a defined class of calls without asking). Reality: walking through a recent case, they could explain their reasoning but hadn't trusted it enough to act without confirmation. Options: they proposed trying it on a low-stakes decision first and reviewing the reasoning after the fact rather than before. Way forward: they committed to making the next reversible decision on their own and bringing the reasoning to the following session, with an explicit offer of support if it went wrong.
Trade-offs and pitfalls
A common mistake is treating GROW as a rigid script and marching through all four stages regardless of what the person actually needs that day. A stronger approach holds the structure loosely: skip Reality if it's already obvious, compress stages under time pressure, and know when the moment calls for direct answers instead of more questions, especially if something is safety-critical or urgent. Inside reviews specifically, overusing Socratic questions when someone is genuinely stuck can read as withholding rather than teaching, so it's worth pairing questions with a clear direct answer once the teaching moment has been made.
A KPI on an executive dashboard suddenly changes and nobody trusts the new number. Walk through how you'd use lineage information to trace it back through transformations to the raw source rows to find where and why it changed, what metadata you'd need captured ahead of time to make that trace fast (transformation SQL, versioning, responsible owner), and how you'd present the trace so a non-technical stakeholder can follow it and trust the fix.
Sample Answer
Start at the KPI's (key performance indicator's) definition and walk the lineage graph backward one hop at a time, checking at each step whether that step's output looks anomalous compared to its historical pattern, which narrows down where the change entered rather than re-deriving the whole pipeline from scratch. Doing this quickly depends on having captured, ahead of time, the transformation SQL for each step, a versioned history of both the schema and the transformation logic, and a responsible owner for each dataset in the chain. Present the result to a non-technical stakeholder as a short, plain-language narrative of the single step that changed, not the full graph.
Tracing back through transformations to raw source rows
Starting from the KPI as rendered on the dashboard, identify its metric definition, the aggregation and filter that produce the number, and the fact table it reads. At each hop upstream, from the fact table to its source transformations, and those to their upstream tables, eventually to raw ingested events, compare the current output to its recent historical values or to a smaller trusted baseline. The hop where the numbers stop looking anomalous relative to a recent, stable baseline is the boundary where the actual cause sits, one step downstream of that boundary. This bisection-style walk, checking a handful of hops rather than every row at every layer, is what makes the trace fast on a deep chain, instead of manually re-running every transformation from raw data forward.
Metadata you need captured ahead of time
- Transformation SQL for each step: without the actual logic recorded, not just "table B comes from table A," you can see that a number changed but not why, since the why usually lives in a filter, join, or aggregation that changed.
- Versioning of both schema and transformation logic: knowing not just what the current SQL is but what it was previously lets you diff and directly see what changed, rather than staring at the current logic and guessing whether it differs from before.
- A responsible owner recorded per dataset: once the boundary hop is found, you need to know who to actually ask or hand the fix to immediately, not after searching for who owns that table.
Presenting the trace to a non-technical stakeholder
Do not hand a stakeholder the dependency graph; translate the finding into a short narrative: what the number is built from, in plain language, and specifically which single step changed and what changed about it, whether a filter got stricter, a source started excluding some rows, or a join key stopped matching for a subset of records, dated against when the KPI's behavior shifted. Pair it with a simple before-and-after comparison at that one step, not the whole chain, so the stakeholder can see the specific cause rather than trusting the summary on faith, and state clearly whether the fix means the new number is correct and the old one was wrong, or the reverse.
Worked example
The weekly active-users KPI drops sharply. The trace starts at the KPI's definition, distinct users with a qualifying event in the trailing 7 days, reading from fct_user_activity. Checking that table's recent values against its trailing average shows it is also lower than expected, so the trace steps one hop further back to the transformation that builds it, which joins dim_user and a raw events table. dim_user's row count looks normal; the events table's row count for the last three days is noticeably below its usual volume. Stepping one more hop back, the transformation SQL that loads events from the raw ingestion source shows a filter excluding test events that was present before but is now unexpectedly also excluding a legitimate new event type introduced by a recent mobile-app release, because a substring match in the filter logic, changed in a deploy three days ago and visible via the versioned transformation history, unintentionally matches the new event type's name. That is the boundary: events looked wrong, its own upstream source did not. The fix is correcting the filter to exclude test events exactly rather than any type containing similar characters, and backfilling the undercounted days.
Presented to the stakeholder: "Weekly active users looked low because a filter change three days ago accidentally excluded a new type of app-open event alongside the test events it was meant to exclude. The undercounted days have been backfilled and today's number is corrected; no real drop in usage occurred."
Trade-offs and pitfalls
The bisection approach only works if enough of the chain actually has captured transformation SQL and version history; any hop where that metadata is missing turns back into manual archaeology at exactly that step, so the design choice with the most payoff is making metadata capture mandatory for every step, not just the most important-looking ones. The presentation pitfall is over-explaining: handing a business stakeholder the full lineage graph or every hop's SQL diff buries the one sentence they actually need, which is what changed and whether they can trust the new number.
Design a rollback strategy for a risky cross-service deployment, for example one that touches payments and can't be uniformly rolled back because traffic is already split across old and new paths. What does the plan need beyond the technical rollback steps themselves?
Sample Answer
Direct answer
When a deployment cannot be rolled back uniformly because traffic is already split across old and new paths, the rollback plan has to be a data-and-transaction plan, not just a code-revert plan: you need a way to make in-flight and already-committed work safe under both versions simultaneously, and a clear rule for when partial rollback is the right call instead of forward-fixing.
Structured elaboration
- Segment rollback by cohort, not globally. Route traffic by user or request cohort behind a flag so the affected slice can be pulled back without touching everyone, and so rollback for a stateless read path and rollback for an in-flight payment can be different operations executed independently.
- Make every payment-affecting operation idempotent (repeating the same operation produces the same result, so a request retried or replayed across old and new code paths cannot get applied twice) and log intent before executing it. Without this, a rollback that re-routes retries to the old path can double-charge or double-refund.
- Use compensating transactions, not raw reverts, for anything already committed. Once money has moved, the deployment cannot be undone and have the transaction un-happen; you issue an offsetting transaction, a refund or a ledger adjustment, with its own audit trail, and durably record intent before the compensating action fires so a crash mid-compensation does not leave the ledger ambiguous.
- Reconcile continuously, and design for detecting silent divergence, not just hard failures. A dedicated reconciliation job comparing the old and new paths' view of the same data catches the case where both paths "succeed" but disagree, which is more dangerous than an outright error because nothing alerts on it by default.
- Freeze the affected surface during rollback, not just the deployment. New transactions above a risk threshold get held for manual review, and finance is looped in explicitly, because the code being rolled back does not mean the financial state is correct until reconciliation confirms it.
Worked example
A payments team ships a change to checkout that also touches profile data and a recommendations service, and roughly a third of traffic is already on the new path when a defect surfaces. Rolling every user back to the old path is not clean: some in-flight orders exist only in the new path's data shape. The response is cohort-scoped: new sessions get routed back to the old path immediately, which is cheap and low-risk; in-flight sessions on the new path are drained rather than force-migrated mid-transaction, using idempotency keys so a retried payment request cannot double-charge regardless of which path handles it. A reconciliation job compares the ledger state written by both paths over the rollback window and flags mismatches for manual review rather than assuming no logged errors means no problem. The honest outcome is that a handful of transactions land in a manual-review queue rather than resolving automatically, and that is the correct outcome, not a gap in the plan: automatically "fixing" an ambiguous financial state is worse than a human checking it.
Trade-offs and pitfalls
The same shape of problem shows up outside payments, and the specific mechanism has to change with it: a model that starts producing harmful output needs a fast output-level kill switch, suppress or fall back, not a slow code rollback, and a core authentication-flow change needs explicit handling of sessions and tokens issued under the new path, because rolling back the code does not retroactively fix a token a user already holds. The main pitfall across all of these is treating the code rollback as the whole plan; teams that only rehearse the deploy-revert step get surprised by the state left behind. The second is over-relying on idempotency without testing it under real concurrency and retries; idempotency keys that were implemented but never load-tested against actual retry storms are a common source of a plan that worked in the tabletop drill but not in the incident.
Design a structure that ingests numbers one at a time from a stream and can report the current median at any point, without re-sorting everything seen so far. Explain how two heaps (keeping them balanced within one element of each other) give you O(log n) insert and O(1) median.
Sample Answer
Direct answer
Keep two heaps: a max-heap (a heap that always exposes its largest element at the top) holding the smaller half of the numbers seen so far, and a min-heap (exposes its smallest element at the top) holding the larger half. Insert into one heap, then relay its top across so the two heaps never differ in size by more than one; the median is then either the top of the larger heap (odd count) or the average of both tops (even count), giving O(log n) insert and O(1) median lookup.
Structured elaboration
Approach
- Maintain the invariant that every element in the lower-half heap is <= every element in the upper-half heap, and the lower heap has either the same size as the upper heap or exactly one more element.
- Python's
heapqonly implements a min-heap, so the lower half is stored as negated values to simulate a max-heap. - On
add_num: always push into the lower (max) heap first, then immediately pop its top and push it onto the upper (min) heap. This guarantees the cross-heap ordering invariant without a separate comparison step, since whatever value the lower heap just received is relayed straight across. Then, if the upper heap has grown larger than the lower heap, move its top back to the lower heap to restore the size invariant. - On
find_median: if the lower heap holds one more element than the upper heap, the median is its top; otherwise it's the average of both tops.
import heapq
class MedianFinder:
def __init__(self):
self.lower = [] # max-heap, stored as negated values
self.upper = [] # min-heap
def add_num(self, num):
heapq.heappush(self.lower, -num)
# move the largest of lower into upper to keep the ordering invariant
heapq.heappush(self.upper, -heapq.heappop(self.lower))
# rebalance sizes: lower may hold at most one extra element
if len(self.upper) > len(self.lower):
heapq.heappush(self.lower, -heapq.heappop(self.upper))
def find_median(self):
if len(self.lower) > len(self.upper):
return float(-self.lower[0])
return (-self.lower[0] + self.upper[0]) / 2.0
Key points
- Pushing into the lower heap first and always relaying its top to the upper heap keeps the cross-heap invariant correct after every insert, instead of needing a conditional comparison.
- The size-rebalancing step after the relay keeps the size difference at most one.
find_mediannever scans a heap; it only reads the O(1)-accessible top or tops.
Worked example
Stream [5, 15, 1, 3, 8, 7, 9, 2], calling add_num then find_median after each value:
stream = [5, 15, 1, 3, 8, 7, 9, 2]
mf = MedianFinder()
medians = [mf.find_median() for x in stream for _ in [mf.add_num(x)]]
print(medians)
prints [5.0, 10.0, 5.0, 4.0, 5.0, 6.0, 7.0, 6.0]. Checking a couple by hand: after inserting 5, 15, 1, the sorted values seen so far are [1, 5, 15], whose median is the middle value 5, matching the third entry. After inserting 5, 15, 1, 3, sorted is [1, 3, 5, 15], an even count, so the median is the average of the two middle values (3 + 5) / 2 = 4, matching the fourth entry.
Trade-offs & pitfalls
Complexity
add_num: O(logn), one push and up to two pop/push pairs on heaps of size up to n/2.
find_median: O(1), reads heap tops only.
Space: O(n) to hold every element seen so far across the two heaps.
Edge cases
- Empty stream:
find_medianhas nothing to read; guard with a length check before calling it, or raise an explicit error. - Single element: the lower heap holds it, the upper heap is empty, and the median is that element.
- Duplicate values: heaps handle duplicates with no special casing, since equal keys just compare equal.
This composition is the standard answer whichever way the question is dressed up: a stream of raw numbers, a stream of test-execution durations where the running-median build time matters, or a generic "support insert and delete operations while answering frequent median queries" data-structure prompt. The invariant and the two heaps are identical across all of these framings; only the label attached to the numbers changes. Note, though, that the design shown here only supports insert cheaply: arbitrary deletion from the stream is not handled by this structure and needs either a lazy-deletion marker scheme or an order-statistics tree, which is the natural next question once delete enters the picture.
A self-balancing binary search tree with subtree-size augmentation gives the same O(log n) insert but only O(log n) median lookup (not O(1)), so two heaps are strictly better for this exact query pattern when arbitrary-rank lookups are never needed, only the median. The most common implementation bug is comparing values directly to decide which heap to push into, instead of always relaying through the lower heap's top first; unit-test the invariant after every insert rather than only checking the final median.
You've inherited a report that gets each user's latest order via a correlated subquery, and it's slow on a large orders table. Show three ways to get the same result: the original correlated subquery, a derived-table join using MAX(created_at), and a ROW_NUMBER() window function. Explain the performance story for each and when you'd genuinely reach for the correlated form anyway.
Sample Answer
The same "latest order per user" result can be built with a correlated subquery, a derived-table join against a pre-aggregated MAX(created_at), or a ROW_NUMBER() window function; they differ in how much per-row work the engine has to redo and in how they handle ties. On a large orders table, the ROW_NUMBER() form is usually the best default because it computes ranks in one ordered pass and lets you break ties deterministically, but the correlated form is still the right call when the predicate is narrow and well-indexed.
Approach 1: correlated subquery
SELECT o.*
FROM orders o
WHERE o.created_at = (
SELECT MAX(o2.created_at)
FROM orders o2
WHERE o2.user_id = o.user_id
);
Approach 2: derived-table join on MAX(created_at)
SELECT o.*
FROM orders o
JOIN (
SELECT user_id, MAX(created_at) AS max_created
FROM orders
GROUP BY user_id
) m ON o.user_id = m.user_id AND o.created_at = m.max_created;
Approach 3: ROW_NUMBER() window function
SELECT order_id, user_id, amount, created_at
FROM (
SELECT o.*,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC, order_id DESC) AS rn
FROM orders o
) t
WHERE rn = 1;
Key points
- Approaches 1 and 2 both compare against
MAX(created_at), so if two orders for the same user share the exact same timestamp, both are returned; neither query has a tiebreaker. - Approach 3 adds
order_id DESCas an explicit tiebreaker, so it always returns exactly one row per user even whencreated_atcollides. ROW_NUMBER()can't be filtered directly in aWHEREclause (window functions evaluate afterWHERE), which is why it has to be wrapped in a subquery, as shown, or filtered withQUALIFYin engines that support it (Snowflake, BigQuery, Databricks; not PostgreSQL or MySQL).
Complexity
The correlated subquery is, logically, one MAX lookup per outer row; whether that's cheap depends entirely on the plan. With an index on (user_id, created_at DESC), a smart optimizer can turn this into an index-descent per user rather than a full re-scan, but many optimizers instead execute it as a nested loop that repeats the inner scan once per outer row, which is the pattern to watch for in EXPLAIN. The derived-table join computes the per-user MAX once, in a single grouped pass over the table (an index or hash aggregate handles this in roughly linear time), then joins that back, which is a bounded amount of work regardless of how many orders a user has. The ROW_NUMBER() form does one sort (or an index-satisfied ordering) per partition, which is generally the same order of work as the grouped aggregate but produces the tiebreaker and the ordering guarantee in the same pass.
Worked example
With five orders across three users, including two orders for user 101 that share the exact same created_at (a same-second data-entry collision), running all three queries against this data shows the difference directly: approaches 1 and 2 both return two rows for user 101 (the tie), while approach 3 with ORDER BY created_at DESC, order_id DESC returns exactly one deterministic row per user, including user 101. (Verified by executing all three queries against SQLite 3.51 with this exact tie case.)
| order_id | user_id | created_at | correlated / derived-join result | ROW_NUMBER() result |
|---|---|---|---|---|
| 2 | 100 | 2024-01-03 09:00:00 | included | included |
| 3 | 101 | 2024-01-02 08:00:00 | included (tie) | excluded |
| 4 | 101 | 2024-01-02 08:00:00 | included (tie) | included (higher order_id wins) |
| 5 | 102 | 2024-01-05 12:00:00 | included | included |
Trade-offs and pitfalls
Genuinely reach for the correlated form when the predicate is highly selective and backed by the right composite index (e.g., you've already filtered down to a handful of users), because a per-row indexed lookup can be cheaper than materializing a full per-user aggregate you don't need elsewhere; some optimizers (PostgreSQL, SQL Server) can automatically rewrite a correlated subquery like this into a semi-join or hash join, but that isn't guaranteed across engines or even across query shapes on the same engine, so don't assume it happened silently. Always check: if EXPLAIN shows a nested loop re-scanning orders once per outer row instead of a hash or merge join, that's the concrete signal to rewrite into the derived-table or ROW_NUMBER() form. The most common bug across all three forms isn't performance, it's silently dropped or duplicated rows from an unhandled tie; if the business logic genuinely needs "the" single latest order, the ROW_NUMBER() form with an explicit tiebreaker is the only one of the three that guarantees it.
Before shipping a new index to production, how would you estimate its benefit and its blast radius? Describe a lightweight before/after benchmarking approach, including how you would guard against a change that measurably helps the one query you tested while quietly increasing load (CPU, write latency, cache pressure) for everything else on the instance.
Sample Answer
Direct answer. Test the candidate index against a realistic copy of production data and a representative slice of the actual query mix, not just the one query you're trying to speed up, measuring both the target query's improvement and any change in resource usage (write latency, cache pressure, overall throughput) for everything else sharing that instance.
Structured elaboration. Estimating benefit for the ONE target query is the easier half: capture a baseline latency and plan shape before the index, add it (ideally on a staging environment with production-representative data volume and distribution, since selectivity and index size behave very differently on a small dev dataset), and compare after. Estimating blast radius is the harder, easier-to-skip half: every additional index adds write-time cost to every INSERT, UPDATE, and DELETE that touches its columns, and adds ongoing background maintenance and cache pressure, none of which shows up if you only measure the one query you added the index to speed up. A test that runs the target query in isolation, on an otherwise idle instance, will systematically overstate the benefit and completely miss the cost, since neither concurrent write load nor cache contention from other queries is present in that setup.
A more honest test replays (or approximates) the instance's actual concurrent write rate alongside the read-side benchmark, and watches for a REGRESSION in unrelated queries' latency or in overall write throughput, not just an improvement in the target query, before concluding the index is a net win.
Worked example. An index that takes one dashboard query from multi-second and sluggish to near-instant in an isolated test looks like an unambiguous win; the same index, tested under the table's real concurrent write load, might reveal a measurable increase in write latency across every other write to that table, which the isolated test never would have surfaced. Only the second test tells you whether the trade is actually worth it for the system as a whole, not just for the one query someone was staring at.
Trade-offs and pitfalls. It's tempting to treat "the target query got faster" as sufficient evidence to ship, since it's the visible, easy-to-demonstrate half of the story; insist on at least a rough measurement of the write-side and concurrency-side cost too, even if it's less precise than the read-side benchmark, since shipping an index that helps one query while quietly degrading everything else is a worse outcome than not shipping it at all.
A production model's performance drops sharply right after a change to the upstream data-ingestion pipeline. Outline a systematic debugging approach: validating raw inputs, comparing feature distributions before and after the pipeline change, verifying schema and null-handling behavior, replaying historical data through the new pipeline to check for silent differences, and using a shadow deployment to isolate whether the regression is in the data or the model. Describe the preventative tests you would add so a future pipeline change can't cause the same regression silently.
Sample Answer
Direct answer
Work from cheapest to most expensive: validate the raw inputs first, then compare feature distributions and verify schema and null-handling on the same data, then replay historical data through the new pipeline to see whether the pipeline code itself behaves differently on identical inputs, and only then reach for a shadow deployment to separate a data-side cause from a model-side one, since a shadow deployment is the most expensive diagnostic and the earlier steps usually already answer the question. The single most common failure this sequence protects against is treating an aggregate, whole-population distribution check as sufficient when the real regression is concentrated in one segment the aggregate view dilutes into invisibility.
Structured elaboration
Validating raw inputs. Before touching any statistics, confirm the pipeline change did not simply break ingestion: row counts per upstream source in the expected range, required fields present, types conforming to the expected schema. This is the cheapest check and catches gross breakage (a source that silently stopped sending a field, a partial ingestion failure) before spending effort on subtler distributional analysis that a gross failure would make meaningless anyway.
Comparing feature distributions before and after the pipeline change. For each feature, compare its distribution from before the change against its distribution from after, using a distribution-free test such as the two-sample Kolmogorov-Smirnov (KS) test, appropriate here because feature distributions are typically continuous and not reliably normal, so a test that does not assume a particular shape is the safer default. That test returns two numbers and the comparison below turns on telling them apart. The KS statistic is the single largest vertical gap between the two samples' cumulative distribution curves (for each value on the horizontal axis, the curve gives the share of that sample falling at or below it), so it runs from 0 when the two distributions are identical to 1 when they do not overlap at all, and it reads directly as "at their widest disagreement, these two samples differ by this much accumulated share." The p-value says only how unlikely a gap that large would be if both samples really did come from the same distribution; it says nothing about how large the gap is. Those come apart at scale: with enough rows, a gap far too small to matter still produces a tiny p-value, so the statistic is the effect size and has to be read alongside the p-value rather than replaced by it. Critically, do this segmented by whatever cohort dimensions are available (region, customer type, input source), not only on the whole population: a change concentrated in one segment can be small enough relative to the whole population that an aggregate-only comparison misses it, while the same comparison restricted to the affected segment shows it clearly.
Verifying schema and null-handling behavior. Confirm explicitly, not by inference, that types, allowed value ranges, and the specific handling of missing values match the contract the current model was trained against. A silent change in null-handling, for example a field that used to arrive as an explicit null and now silently gets coerced to zero by an upstream default, produces a systematic, hard-to-spot shift that a generic distribution comparison can sometimes miss if the coerced value happens to fall within an otherwise plausible range.
Replaying historical data through the new pipeline. Take a fixed batch of raw data from before the change, whose resulting features were already recorded by the old pipeline, and run that identical raw batch through the new pipeline code. Diff the newly computed features against the originally recorded ones for the exact same input rows. This is the cleanest possible causal test available: because the raw input is literally identical, any difference in the output features can only come from the pipeline code itself, not from the real world having changed, which is exactly the distinction needed to separate "the code changed behavior" from "the underlying data genuinely shifted."
Using a shadow deployment to isolate data versus model. Once the cheaper checks above have narrowed things down, run the current production model against the new pipeline's live features in shadow mode, scored but never served to users, and compare its offline performance on a labeled sample of that shadow traffic against its known historical performance. If the same, unchanged model degrades when fed the new pipeline's features, the fault sits in the features, not the model. If a retrain is also under consideration, comparing the old model and a newly retrained model against the identical new feature set is what isolates whether any remaining gap is model-side rather than data-side.
Preventative tests for the future. Turn the historical-replay diff from an ad hoc investigation step into an automated regression test: a fixed historical batch with its expected feature output checked into the test suite, run automatically whenever the pipeline code changes, so a future silent behavior change fails a test instead of reaching production. Add an explicit schema and null-handling contract test that asserts the exact type, range, and null-treatment behavior the model depends on. Add a scheduled, segmented distribution-drift monitor comparing live feature distributions against the training-time reference on an ongoing basis, not only around known pipeline changes, since not every silent regression will coincide with a deploy someone remembers to check against.
Worked example
Comparing an order_value feature before and after a pipeline change that silently broke currency normalization for international orders only, on synthetic data (4,000 rows before, 4,000 after, about 25% flagged international in each), first at the whole-population level, then segmented. The seed is pinned so every number below is reproducible rather than a one-off draw:
import numpy as np
from scipy import stats
N, INTL_SHARE, BUG_FACTOR = 4000, 0.25, 1.35
MU, SIGMA = np.log(40.36) - 0.125, 0.5 # order_value is lognormal, mean about 40
rng = np.random.default_rng(5268) # pinned, so this table reproduces
before = rng.lognormal(MU, SIGMA, N)
before_intl = rng.random(N) < INTL_SHARE
after = rng.lognormal(MU, SIGMA, N)
after_intl = rng.random(N) < INTL_SHARE
# The bug: currency normalization silently mis-scales international orders only.
after = np.where(after_intl, after * BUG_FACTOR, after)
comparisons = [
("whole population", before, after),
("domestic segment only", before[~before_intl], after[~after_intl]),
("international segment only", before[before_intl], after[after_intl]),
]
print(f"{'comparison':<28}{'n before':>9}{'n after':>9}{'KS stat':>10}{'p-value':>12}")
for label, a, b in comparisons:
res = stats.ks_2samp(a, b)
print(f"{label:<28}{len(a):>9}{len(b):>9}{res.statistic:>10.4f}{res.pvalue:>12.2g}")
m_before, m_after = before[before_intl].mean(), after[after_intl].mean()
print(f"\ninternational mean order_value: {m_before:.2f} -> {m_after:.2f} "
f"(ratio {m_after/m_before:.4f}, bug applied {BUG_FACTOR})")
Output:
comparison n before n after KS stat p-value
whole population 4000 4000 0.0610 6.8e-07
domestic segment only 2994 3000 0.0188 0.65
international segment only 1006 1000 0.2441 1e-26
international mean order_value: 40.08 -> 54.11 (ratio 1.3499, bug applied 1.35)
| comparison | KS statistic | p-value |
|---|---|---|
| whole population | 0.0610 | 6.8e-07 |
| domestic segment only | 0.0188 | 0.65 |
| international segment only | 0.2441 | 1e-26 |
The whole-population test does detect something (p=6.8e-07), but its KS statistic of 0.061 looks like a mild, easy-to-dismiss shift. Read literally, 0.061 says that at the point where the before and after cumulative curves are furthest apart they differ by about 6 percentage points of accumulated mass, which on a 0-to-1 scale is close to the identical end. With 4,000 rows on each side, even a gap that small is comfortably significant, and that combination, a tiny statistic with a convincing p-value, is exactly the trap: read the p-value alone and it looks like a confirmed problem, read the statistic alone and it looks like nothing, and neither reading tells you where to go next. Segmenting shows what actually happened: the domestic segment shows no significant difference at all (p=0.65, indistinguishable from noise), while the international segment alone shows a far larger and far more significant shift (KS statistic 0.244, p effectively zero). On the same 0-to-1 scale, 0.244 means those two curves separate by over 24 percentage points of accumulated mass at their widest, exactly four times the whole-population gap, and it does that on only about a quarter of the rows, which is precisely why averaging it in with the unaffected three quarters shrank it to 0.061. The international segment's mean order value moved from 40.08 to 54.11, a ratio of 1.3499, recovering almost exactly the 1.35x mis-scaling the underlying bug actually applied. An investigation that stopped at the whole-population number would have seen a modest, ambiguous signal; segmenting turned it into an unambiguous, localized, and nearly root-cause-identifying result.
Trade-offs and pitfalls
- Reaching for the shadow deployment before the cheaper checks wastes the most expensive tool on a question the earlier steps usually already answer. Sequencing this from cheapest to most expensive is not just tidiness, it avoids spending shadow-deployment effort re-discovering what a distribution comparison would have shown directly.
- An aggregate-only distribution comparison can genuinely miss a real, severe, segment-concentrated regression, exactly as the worked example shows; always segment by every cohort dimension available before concluding a feature is unaffected.
- The historical-replay diff is the step most often skipped, and it is the one that actually distinguishes a code bug from a genuine real-world shift. Without it, a team can spend real effort investigating "why did the world change" when the honest answer is "the pipeline code changed and the world did not."
- A replay test only covers the inputs it was built from. It will not catch a bug that only manifests on an input pattern the historical batch never contained, so it complements, rather than replaces, the ongoing distribution-drift monitor.
A cluster reports repeated 'FetchFailed' and 'ExecutorLostFailure' errors during a heavy shuffle stage. Describe a methodical approach to troubleshoot these failures: which logs and metrics to inspect, how to distinguish network, disk, or executor OOM causes, and short-term mitigations to restore job stability.
Sample Answer
Direct answer
FetchFailed and ExecutorLostFailure during a heavy shuffle stage most commonly trace to one of three distinct causes, and the Spark UI plus infrastructure-level logs together usually distinguish them: an executor OOM (out of memory) killed by the cluster manager (check for Killed status and memory-limit-exceeded messages in executor logs), a genuine network problem between executors (check fetch-wait times and infrastructure network metrics), or a disk problem on the node holding shuffle files (check disk I/O/capacity metrics on the specific host). Short-term, restoring stability usually means addressing whichever of these three is confirmed (reduce shuffle partition size for OOM, retry/reroute for network, drain the bad node for disk) rather than a generic "just retry and hope."
Structured elaboration
Which logs and metrics to inspect, in order.
- Spark UI, Stages tab: confirm the failure pattern (which specific tasks failed, whether failures cluster on specific executors/hosts) before going to raw logs; this narrows the investigation the same way.
- Executor logs for the SPECIFIC lost executor (via the History Server or the cluster manager's own log aggregation, e.g. YARN's
yarn logs -applicationId ...): look explicitly for an OOM-kill signature (aContainer killed by YARN for exceeding memory limitsmessage on YARN, or the Kubernetes equivalent,OOMKilledinkubectl describe podfor that executor's pod) versus a clean process exit versus no trace at all (suggesting the HOST itself became unreachable, not just the process). - Cluster-manager-level events (YARN ResourceManager/NodeManager logs, Kubernetes events for the node/pod) for the affected host/node around the failure timestamp: a node marked unhealthy, a node drain/preemption event, or a resource-pressure signal independent of anything Spark itself logged.
- Node-level infrastructure metrics (outside Spark's own visibility): disk I/O latency/queue depth and disk free space for the specific host (a disk nearing capacity or showing elevated I/O latency directly threatens shuffle-file writes/reads), and network throughput/error counters for that host (packet loss, retransmits) if a network cause is suspected.
Distinguishing the three causes.
- Executor OOM: confirmed by an explicit memory-limit-exceeded kill message in the executor or cluster-manager logs; commonly caused by shuffle partitions sized too large for available executor memory (the sizing heuristics), or by a memory leak/unusually large broadcast variable consuming more heap than expected.
- Network: confirmed by elevated
FetchFailedfetch-wait times specifically (not just failures, but SLOW successful fetches preceding the failures) and, ideally, corroborating infrastructure-level network metrics for the specific hosts involved; genuinely transient network blips may not leave a clean smoking gun in Spark's own logs alone. - Disk: confirmed by disk-capacity or I/O-latency anomalies on the specific host holding the failed shuffle files, sometimes also visible as
Shuffle Spill (Disk)anomalies or explicit disk-write-failure exceptions in executor logs if the disk was actually full rather than just slow.
Short-term mitigations to restore job stability, matched to the confirmed cause.
- OOM: increase
spark.executor.memoryOverheadand/or reduce shuffle partition SIZE (more, smaller partitions) for this specific job as an immediate fix, alongside the postmortem-level investigation into whether this was a one-off data-volume spike or a persistent undersizing. - Network: increase Spark's own shuffle-fetch retry tolerance (
spark.shuffle.io.maxRetries,spark.shuffle.io.retryWait) as an immediate buffer against transient blips, while escalating the underlying network issue to infrastructure if it recurs. - Disk: drain/cordon the specific affected node from receiving new tasks immediately (preventing further failures from that specific bad disk) while infrastructure investigates or replaces it, and re-run the job so it is scheduled onto healthy nodes.
Worked example
A shuffle stage shows 40 tasks with FetchFailed, all attempting to fetch from executors that were running on 3 specific hosts (node-11, node-11, node-27, note node-11 appears twice, associated with 2 different executor IDs that ran on it at different times during the job).
Checking executor logs for the lost executors on node-11 shows explicit YARN container-killed-for-exceeding-memory-limits messages for BOTH executor instances that ran there; node-27's lost executor log shows a clean process exit with no memory-limit message, but the NodeManager log for node-27 around the same timestamp shows a node-health-check failure and the node being marked temporarily unhealthy. Conclusion: TWO distinct root causes present in the same incident, not one: node-11's repeated OOM kills point to genuine memory undersizing for this job's shuffle partition sizes (the job needs more memoryOverhead or smaller shuffle partitions, a Spark-configuration fix), while node-27's health-check failure points to a node-level infrastructure problem independent of the job's own configuration (an infrastructure investigation, not a Spark tuning fix). Treating both failures as "the same shuffle problem" and applying only a memory-sizing fix would have left node-27's underlying infrastructure issue unaddressed and likely to recur on a future job.
Trade-offs and pitfalls
- Common mistake: treating every
FetchFailed/ExecutorLostFailureas the same failure mode and applying one blanket fix (usually "just retry" or "just add more memory") without the log-level differentiation shown above; as the worked example demonstrates, a single incident can genuinely have MULTIPLE distinct root causes simultaneously, and averaging them into one story misses at least one of the real fixes needed. - Common mistake: relying only on Spark's own logs and UI without cross-referencing cluster-manager and infrastructure-level signals; several of the concrete distinguishing evidence above (node-health-check failures, disk capacity, network error counters) live OUTSIDE what Spark itself logs, and a purely Spark-scoped investigation can misdiagnose an infrastructure problem as a Spark configuration problem or vice versa.
- Increasing retry tolerance (
spark.shuffle.io.maxRetries) as a short-term mitigation is appropriate for a genuinely transient issue but can mask a persistent, worsening infrastructure problem if applied as a permanent fix without also tracking whether the underlying failure rate is trending up over time; treat it as a buffer while the real cause is investigated, not a substitute for fixing the real cause. - Executor loss correlated with SPECIFIC hosts, persisting across multiple jobs or multiple days (the same signal), is the strongest indicator that a node needs infrastructure attention rather than another round of Spark-side configuration tuning.
Design a system to orchestrate a hierarchy of pre-aggregated tables with dependencies (base fact -> hourly -> daily -> monthly), and route each dashboard query to the correct rollup based on the requested date range and granularity. Explain the incremental refresh algorithm, how you minimize recomputation when an upstream change occurs, and how you handle failure recovery so aggregates stay correct.
Sample Answer
Orchestrating dependent rollups (a base fact feeding an hourly aggregate, which feeds daily, which feeds monthly) means treating the whole chain as a dependency graph, not a set of independently scheduled jobs, and routing each incoming dashboard query to the coarsest rollup that can still answer it correctly.
Dependency-aware refresh
Model each rollup's refresh as depending on the freshness of the level below it: hourly cannot legitimately advance past what the base fact has ingested, daily cannot advance past what hourly has completed, and so on up the chain. A scheduler that tracks each level's watermark (a marker, typically a timestamp or a monotonically increasing id, recording how much of the upstream data that level has already incorporated) can then trigger the next level's refresh automatically once its dependency's watermark advances, rather than running every level on a fixed clock and hoping the timing lines up.
Query routing
Route an incoming query to the coarsest rollup whose grain matches (or can be trivially summed to match) the requested date range and dimensions: a query for "total revenue this month" should hit the monthly rollup directly rather than summing 720 hourly rows or reading the base fact, while a query for "revenue in the last 3 hours" has to fall back to the hourly (or base) level since no coarser rollup has fine enough grain. This routing logic needs an explicit mapping from query shape to the cheapest rollup that can serve it, checked before the query runs.
Minimizing recomputation
When an upstream correction happens (a late-arriving or corrected base-fact row), only the specific hourly buckets it falls into need to be recomputed, and only the daily and monthly rollups that include those specific hours need to be redone, not the entire history. Track which downstream rollup buckets each base-fact time range feeds, so a correction's blast radius is bounded to exactly the buckets it touches.
Failure recovery
If an hourly refresh fails partway, daily and monthly should not advance on the assumption it succeeded; the dependency-watermark tracking above naturally prevents this since daily's own refresh will see that hourly's watermark did not move and will correctly wait rather than working from stale or missing hourly rows.
Trade-offs and pitfalls
The query-routing logic is the piece most likely to go stale silently: as new dashboard query patterns emerge that do not map cleanly onto an existing rollup grain, they will quietly fall back to the most expensive path (the base fact) unless someone notices and adds a routing rule or a new rollup grain. Periodically audit which grain each dashboard's queries are actually being served from, since the whole design's value depends on that routing staying correct as usage evolves.
A company with heavy regulatory audit requirements and frequent backfills is choosing between Lambda and Kappa architecture for its aggregation/reporting platform. Analyze operational complexity, duplication of logic, storage costs, and correctness guarantees, and recommend an approach with justification. Would your recommendation change for a small team with limited SRE resources?
Sample Answer
Direct answer
For an organization with heavy regulatory audit requirements and frequent backfills, I'd lean Lambda, not Kappa, because auditors and backfills both want a batch layer that recomputes deterministically from the immutable historical record, which is exactly what Lambda's batch layer already is; asking a purely-streaming system to serve that role means your regulatory correctness story depends entirely on your streaming engine's replay guarantees, which is a riskier place to put it.
Structured elaboration
Operational complexity: Lambda genuinely costs more day to day (two systems to run, two deploy pipelines, two on-call surfaces), but for a regulated business, that cost buys you something auditors specifically want: a batch recomputation that is simple to explain, easy to freeze and re-run against a specific historical snapshot, and doesn't depend on the availability or configuration of a live streaming cluster at audit time.
Duplication of logic: this is Lambda's real weakness, and in a regulated environment it's worse than usual, because a divergence between the batch and speed layer isn't just an engineering embarrassment, it can be a compliance finding ("which number is the official one?"). The mitigation is to push as much shared logic as possible into a common library (or a framework like Apache Beam that can target both a batch and a streaming runner from one codebase), rather than accept two independent implementations.
Storage costs: both approaches need to retain full history for backfills either way (Kappa needs it for replay, Lambda's batch layer needs it for recompute), so this axis is close to a wash; Lambda's marginal storage cost is really just the speed layer's short-lived recent-window state, which is small.
Correctness guarantees: this is where Lambda wins for this specific org. The batch layer is the audit-facing source of truth, computed the same deterministic way every time from an immutable log, and the speed layer is explicitly labeled as "provisional, not yet reconciled," which is a distinction regulators understand. A backfill in Lambda just means rerunning the batch layer over a corrected range; in Kappa it means replaying the single streaming codebase, which, if that codebase has since changed, can produce a subtly different result for old data than what actually shipped at the time, which is a genuinely uncomfortable position to be in during an audit.
Worked example
Concretely: this company discovers in Q3 that a transaction was miscategorized in Q1. Under Lambda, the fix is: correct the source record, rerun the Q1 batch job, republish the corrected view, and the batch layer's determinism means the new number is defensible on its own. Under Kappa, the fix requires replaying the stream from Q1 through today's version of the processing code, and now you have to prove to an auditor that today's code, run against Q1 data, produces the same result the business actually reported in Q1, which is a much harder claim to substantiate if the code has evolved.
Trade-offs and pitfalls
A small team with limited SRE resources changes this calculus, but not the recommendation, only the mitigation: they should still lean Lambda for the audit story, but invest hard in sharing logic (Beam, or a well-tested shared library called from both layers) rather than accepting two hand-maintained pipelines, because a small team maintaining two fully independent codepaths is where Lambda's operational cost actually becomes unsustainable. The mistake to avoid is picking Kappa purely for the team's operational convenience while ignoring that it shifts audit risk onto code-evolution guarantees the team may not actually be able to make.
Recommended Additional Resources
- DataLemur - Practice Google SQL interview questions with real problems
- LeetCode - Data-specific algorithm and system design problems
- Google Cloud Platform Documentation - Official guides for BigQuery, Dataflow, Pub/Sub, and other GCP services
- Designing Data-Intensive Applications by Martin Kleppmann - Essential reading for data systems design
- System Design Interview by Alex Xu - Comprehensive guide to system design thinking
- Google Cloud Skills Boost - Official GCP training platform with hands-on labs
- Blind and Levels.fyi - Community resources with anonymized Google interview feedback
- Apache Spark and Hadoop documentation - Deep dive into distributed computing frameworks
- Stanford CS345 Distributed Databases course - Advanced concepts in distributed data systems
- Google Papers on data infrastructure - Read published research on systems like Bigtable, Dremel (BigQuery), and Spanner
Search Results
Google Data Engineer Interview in 2025 (Leaked Questions)
ETL Pipelines Questions · Can you explain how you would optimize a large-scale data pipeline? · How would you implement a real-time streaming ...
GCP Data Engineer Interview Questions and Answers For Freshers ...
We have compiled the most frequently asked GCP Data Engineering Interview Questions and Answers for 2025, specifically curated from real interview experiences ...
Google Data Engineer Interview (process, questions prep)
As mentioned previously, Google will ask you questions that fall into these categories: behavioral, SQL, coding, and data management questions.
14 Google SQL Interview Questions (Updated 2025) - DataLemur
To help you land your dream data/analytics job in data at Google, practice these 14 REAL Google SQL interview questions which we've curated and solved for you.
Top 90+ Data Engineer Interview Questions and Answers
The article will cover over 90+ Data Engineering interview questions, from simpler concepts to advanced topics.
Google Interview Questions: The Ultimate Guide (2026)
Prepare for your Google interview with our up-to-date guide covering the full process, role-based questions, preparation tips, ...
Data Engineering Interview Questions and Answers (2025 Guide)
These are some of the most common data engineering interview questions and answers. Verified: These are real questions, reported and verified by hiring ...
This interview preparation guide was generated using AI-powered research from the sources listed above. While we strive for accuracy, we recommend verifying critical information from official company sources.
Want to create your own tailored preparation guide using our deep research?
Get Started for FreeInterview-Ready Courses
Visual-first, interactive, structured learning paths