GCP Interview Questions for Data Engineers, Part 2: The Apache Beam Coding Round

Dataflow roles often include a short Apache Beam coding exercise. The tasks are small, but they show whether you think in PCollections and combiners or in loops. Here are five typical tasks, solved and run locally on real data, with what each one tests and the follow-up questions interviewers ask.

GloryTecks Team

Training & Placement Faculty

6 May 2026 8 min read

Last updated: 10 October 2026

Share:

Part 1 of the GCP interview questions for data engineers covers the concepts: event time, watermarks, Pub/Sub delivery, storage classes, IAM and choosing between Dataflow and Dataproc. For roles that work on Dataflow, interviewers often add a short coding exercise in Apache Beam, the SDK that Dataflow runs. The tasks are small, but they show quickly whether a candidate thinks in PCollections, keys and combiners, or tries to write loops over lists.

Below are five typical tasks, solved and run locally on real data: hotel bookings from two Portuguese hotels (Antonio, de Almeida and Nunes, 2019) and hourly bike rentals in Washington, D.C. (UCI: Bike Sharing). No runner was specified, and the run's logs show Beam's Prism runner executing the pipelines locally (Beam: Prism runner). On Google Cloud, the same code would run on Dataflow by changing pipeline options.

python
import csv, glob, os, tempfile, logging
from datetime import datetime, timezone
import apache_beam as beam
from apache_beam.transforms import window
logging.getLogger().setLevel(logging.ERROR)

OUT = tempfile.mkdtemp()
def collect(name):                                  # read back what a pipeline wrote, sorted for stable output
    rows = []
    for f in glob.glob(os.path.join(OUT, name + "*")):
        rows += [line.rstrip("\n") for line in open(f, encoding="utf-8")]
    return sorted(rows)

def bookings():
    with open("hotels.csv", newline="", encoding="utf-8") as f:
        yield from csv.DictReader(f)

with beam.Pipeline() as p:                          # no runner given: Beam uses its local runner (Prism here)
    rows = p | "read" >> beam.Create(list(bookings()))
    kept = rows | "not cancelled" >> beam.Filter(lambda r: r["is_canceled"] == "0")

    # Task 1: bookings per market segment (count per key)
    (kept | "segment" >> beam.Map(lambda r: r["market_segment"])
          | "count" >> beam.combiners.Count.PerElement()
          | "fmt1" >> beam.MapTuple(lambda k, n: f"{k}: {n}")
          | "w1" >> beam.io.WriteToText(os.path.join(OUT, "t1")))

    # Task 2: average nightly rate per hotel, with a combiner rather than GroupByKey
    (kept | "rate" >> beam.Map(lambda r: (r["hotel"], float(r["adr"])))
          | "mean" >> beam.combiners.Mean.PerKey()
          | "fmt2" >> beam.MapTuple(lambda k, v: f"{k}: {v:.2f}")
          | "w2" >> beam.io.WriteToText(os.path.join(OUT, "t2")))

    # Task 3: top 3 guest countries per hotel
    (kept | "country" >> beam.Map(lambda r: ((r["hotel"], r["country"]), 1))
          | "per country" >> beam.CombinePerKey(sum)
          | "rekey" >> beam.MapTuple(lambda k, n: (k[0], (n, k[1])))
          | "top3" >> beam.combiners.Top.PerKey(3)
          | "fmt3" >> beam.MapTuple(lambda h, top: f"{h}: " + ", ".join(f"{c} {n}" for n, c in top))
          | "w3" >> beam.io.WriteToText(os.path.join(OUT, "t3")))

    # Task 4: enrich with a small lookup table passed as a side input
    hotel_city = p | "lookup" >> beam.Create([("City Hotel", "Lisbon"), ("Resort Hotel", "Algarve")])
    (kept | "hotel key" >> beam.Map(lambda r: r["hotel"])
          | "enrich" >> beam.Map(lambda h, cities: cities.get(h, "unknown"), cities=beam.pvalue.AsDict(hotel_city))
          | "count city" >> beam.combiners.Count.PerElement()
          | "fmt4" >> beam.MapTuple(lambda k, n: f"{k}: {n}")
          | "w4" >> beam.io.WriteToText(os.path.join(OUT, "t4")))

class LabelWithDay(beam.DoFn):                     # attach the window's start date to each daily total
    def process(self, total, win=beam.DoFn.WindowParam):
        yield f"{datetime.fromtimestamp(win.start.micros / 1e6, timezone.utc):%Y-%m-%d}: {total}"

with beam.Pipeline() as p:
    # Task 5: daily totals in event time, from hourly bike rentals
    def stamped(r):
        # the file gives local hours; they are used as-is, so windows follow the local calendar day
        ts = datetime.strptime(f"{r['dteday']} {int(r['hr']):02d}", "%Y-%m-%d %H").replace(tzinfo=timezone.utc)
        return window.TimestampedValue(int(r["cnt"]), ts.timestamp())
    with open("hour.csv", newline="", encoding="utf-8") as f:
        hours = [r for r in csv.DictReader(f) if r["dteday"].startswith("2012-07-0")]
    (p | "hours" >> beam.Create(hours)
       | "event time" >> beam.Map(stamped)
       | "daily" >> beam.WindowInto(window.FixedWindows(24 * 3600))
       | "sum" >> beam.CombineGlobally(sum).without_defaults()
       | "label" >> beam.ParDo(LabelWithDay())
       | "w5" >> beam.io.WriteToText(os.path.join(OUT, "t5")))

for task, title in [("t1", "T1 non-cancelled bookings per market segment"), ("t2", "T2 mean nightly rate per hotel"),
                    ("t3", "T3 top 3 guest countries per hotel"), ("t4", "T4 bookings by city (side-input lookup)"),
                    ("t5", "T5 daily bike rentals, 1-9 July 2012 (fixed event-time windows)")]:
    print(title)
    for line in collect(task):
        print("  ", line)

with open("hour.csv", newline="", encoding="utf-8") as f:     # cross-check task 5 without Beam
    check = sum(int(r["cnt"]) for r in csv.DictReader(f) if r["dteday"] == "2012-07-04")
print("check: 2012-07-04 summed directly from the file:", check)
text
T1 non-cancelled bookings per market segment
   Aviation: 185
   Complementary: 646
   Corporate: 4303
   Direct: 10672
   Groups: 7714
   Offline TA/TO: 15908
   Online TA: 35738
T2 mean nightly rate per hotel
   City Hotel: 105.75
   Resort Hotel: 90.79
T3 top 3 guest countries per hotel
   City Hotel: PRT 10879, FRA 7081, DEU 5012
   Resort Hotel: PRT 10192, GBR 5923, ESP 3106
T4 bookings by city (side-input lookup)
   Algarve: 28938
   Lisbon: 46228
T5 daily bike rentals, 1-9 July 2012 (fixed event-time windows)
   2012-07-01: 5531
   2012-07-02: 6227
   2012-07-03: 6660
   2012-07-04: 7403
   2012-07-05: 6241
   2012-07-06: 6207
   2012-07-07: 4840
   2012-07-08: 4672
   2012-07-09: 6569
check: 2012-07-04 summed directly from the file: 7403

Task 1: count per key

What it tests. Whether you reach for a built-in transform. Count.PerElement counts each distinct value, here market segments, in one line. Writing a GroupByKey and counting the list yourself works, but suggests you do not know the library.

Follow-up. "What happens on Dataflow with 100 million rows?" Explain that counting is a combine, which can be partly computed on each worker before data is shuffled.

Task 2: an average per key

What it tests. Combiners. Mean.PerKey computes the average nightly rate per hotel without collecting every value for a key in one place. Beam's programming guide describes combiner lifting: values are combined per key and window before the shuffle, which can cut the data shuffled by orders of magnitude (Beam programming guide). A GroupByKey followed by your own averaging is equivalent in result, but holds all of a key's values together, which becomes a problem with a hot key such as one huge hotel.

Follow-up. "Write the mean as your own CombineFn." The answer is an accumulator of sum and count, a merge that adds them, and an output that divides. A sum of averages does not work.

Task 3: top N per key

What it tests. Re-keying. To find the three largest guest countries per hotel, first count per (hotel, country), then re-key by hotel with (count, country) as the value, then apply Top.PerKey(3). Portugal led both hotels; France and Germany followed at the city hotel, Britain and Spain at the resort.

Follow-up. "How do ties work?" Tuples compare element by element, so equal counts are ordered by country code, a detail worth stating.

Task 4: enrich with a lookup

What it tests. Side inputs. A small table mapping hotels to their location is passed to Map as a side input with AsDict, an additional input available while processing each element. The result: 46,228 non-cancelled bookings at the Lisbon hotel and 28,938 in the Algarve.

Follow-up. "What if the lookup has ten million rows?" A side input is fine for small reference data. For large tables, join with CoGroupByKey instead, or look up from a service with caching.

Task 5: totals per day in event time

What it tests. Windowing. Each hourly record is given a timestamp, then FixedWindows of one day groups records by when the rentals happened, not when the pipeline read them. Summing per window gave daily totals for 1 to 9 July 2012. The check at the end recomputes 4 July straight from the file: 7,403 both ways. Including a check like that in an interview shows discipline.

Follow-up. "What if some records arrive two days late?" That leads to watermarks, allowed lateness and triggers, covered in part 1. Note also that the timestamps here are local hours used as-is. In production, decide explicitly which time zone defines a day.

"How would you test this?"

Expect this question after any coding task. A good answer has three parts. First, unit-test the functions inside the transforms, such as the parser or the re-keying lambda, as plain Python. Second, test each transform on a handful of hand-made elements with Beam's testing utilities, TestPipeline with assert_that and equal_to from apache_beam.testing, so the expected output is written down next to the input. Third, for windowed logic, test with explicit timestamps, including one element that arrives late. Mentioning that you would cross-check one number against a simpler calculation, as Task 5 does, shows you treat pipelines like any other code that can be wrong.

How to do well in the Beam round

  • Name the transforms you would use before typing: filter, map to key-value, combine, window, write.
  • Prefer combiners over grouping then aggregating.
  • Say what scales and what does not: hot keys, large side inputs, unbounded state.
  • Do not depend on output order: Beam does not promise it, which is why the code sorts results before printing.

For a full streaming pipeline, see Pub/Sub to Dataflow to BigQuery, and for Dataflow itself, the Dataflow tutorial.

What Apache Beam questions are asked in GCP data engineering interviews?

Typical tasks are counting or averaging per key, top N per key, enriching data with a side input or a join, and windowing by event time, followed by questions on scale, hot keys and late data.

Why use CombinePerKey instead of GroupByKey?

Combines can be partly computed before the shuffle, which reduces the data moved between workers and avoids holding all of a key's values in one place. The result is the same as grouping and then aggregating.

What is a side input in Beam?

An additional input, such as a small lookup table, that a transform can read while processing each element. It suits small reference data; large tables should be joined instead.

Can I run Apache Beam pipelines without Google Cloud?

Yes. Beam's local runners run pipelines on your machine for development and tests; the same code runs on Dataflow by changing the pipeline options.

Streaming and batch pipelines with Beam and Dataflow are part of the Data Engineering course.

Sources

GloryTecks Team

Training & Placement Faculty

The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.

Want to master GCP?

Join GloryTecks’ expert-led Data Engineering training in Hyderabad with real-time projects and 100% placement support.

Explore course