All jobs / Data engineer

Programming interview questions

Programming questions a Data engineer can be asked, from first job to senior. Open one to see the strongest answer and why it works.

  1. def add(item, bag=[]): bag.append(item); return bag — you call add(1) then add(2). What comes back the second time?

    The strongest answer: [1, 2] — the default list is created once when the function is defined, so every call without a bag shares it; use bag=None and build a new list inside

    Why it works: Exactly. One default object, shared for the life of the function. None plus a fresh list inside is the fix.

    • Default arguments are evaluated once, at definition.
    • A mutable default is shared by every call.
    • Use None and create the value inside the function.
    • The bug is silent, not an error.
  2. When would you choose a tuple over a list?

    The strongest answer: When the collection is fixed and its position means something — a coordinate, a record, a dict key — since tuples are immutable and therefore hashable

    Why it works: Right. Immutability is the point, and hashability is what it buys you.

    • Tuple = fixed, positional, hashable.
    • List = a sequence you intend to change.
    • Only a tuple can key a dict or join a set.
    • Choose on meaning, not on length.
  3. What is the difference between == and is?

    The strongest answer: == asks whether two values are equal; is asks whether they are the very same object — use is only for None, True, False and sentinels

    Why it works: Exactly. Identity versus equality, and is belongs to singletons.

    • == compares values, is compares identity.
    • Use is None, not == None.
    • Equal objects need not be the same object.
    • Caching makes is look right by accident.
  4. You want a value from a dict, or a default if the key is missing, without raising. What do you use?

    The strongest answer: d.get(key, default) — or d.setdefault / defaultdict when the missing key should also be stored

    Why it works: Right. get for a read; setdefault or defaultdict when the miss should create the entry.

    • get(key, default) for a safe read.
    • setdefault / defaultdict when the miss should stick.
    • in then index looks up twice.
    • or swallows falsy stored values.
  5. if items: — what exactly is being tested?

    The strongest answer: Whether items is truthy: an empty list, empty string, empty dict, 0 and None are all falsy, so this is not the same as testing for None

    Why it works: Exactly — which is why an empty list and None take the same branch unless you separate them.

    • Empty containers, 0 and None are all falsy.
    • if x is not None when None is the thing you mean.
    • if x when any empty value should take the same branch.
    • Be explicit when the two differ.
  6. Building a long string in a loop with s += piece is slow on big inputs. Why?

    The strongest answer: Strings are immutable, so each += builds a whole new string and copies what came before; collect the pieces in a list and "".join(parts) once

    Why it works: Right. Copy per iteration versus one allocation at the end.

    • Strings cannot be changed in place.
    • += in a loop copies repeatedly.
    • Collect into a list, then join.
    • The cost grows with the square of the length.
  7. What does xs[1:4] give you, and what does xs[:] do?

    The strongest answer: Items at index 1, 2 and 3 — the stop is exclusive — and xs[:] is a shallow copy of the whole list

    Why it works: Exactly. Half-open ranges, and a bare slice copies the outer list only.

    • Stop is exclusive: [1:4] is 1, 2, 3.
    • xs[:] is a shallow copy.
    • Slices clamp instead of raising.
    • Nested objects are still shared after a shallow copy.
  8. You need both the index and the item while looping. What is the idiomatic way?

    The strongest answer: for i, item in enumerate(items) — and enumerate(items, start=1) when the count should begin at one

    Why it works: Right, and the start argument saves the off-by-one arithmetic.

    • enumerate gives index and item together.
    • start=1 for human-facing numbering.
    • range(len(x)) is a smell in review.
    • Manual counters drift.
  9. When is a list comprehension the right choice over a for loop?

    The strongest answer: When you are building one collection from another with a simple expression and filter; once it needs branching, side effects or two levels of nesting, a loop reads better

    Why it works: Exactly. It is a readability call, and the line where it flips is nesting.

    • Build-from-iterable with a simple expression.
    • Side effects belong in a loop.
    • Two levels of nesting is the readability limit.
    • A generator expression when you do not need the list.
  10. A CSV file loads with the wrong number of columns in some rows. What is usually going on, and how do you read it safely?

    The strongest answer: Quoted fields containing commas, quotes or newlines that a naive split does not respect — or a different delimiter or encoding. Use a real CSV parser with declared quoting and encoding, and validate the column count per row

    Why it works: Right — CSV is a quoting convention, not a format; a parser that honours it plus per-row validation is the safe read.

    • Use a CSV parser, never split.
    • Declare quoting and encoding.
    • Validate column count per row.
    • Quarantine what still fails.
  11. rows2 = rows[:] then rows2[0].append(9). Why did rows change too?

    The strongest answer: The slice copied the outer list but not the inner lists, so both lists hold the same row objects — copy.deepcopy, or build new inner lists, if you need them separate

    Why it works: Right. One level deep is exactly what a shallow copy means.

    • A slice copies one level.
    • Inner objects stay shared.
    • deepcopy, or rebuild the inner values.
    • The same trap applies to dicts of dicts.
  12. What is wrong with except: pass?

    The strongest answer: It swallows everything including typos, KeyboardInterrupt and bugs you needed to see; catch the specific exception you can actually handle, and log anything you deliberately ignore

    Why it works: Exactly. It hides the failures that would have told you what was wrong.

    • Catch the exception you can handle.
    • A bare except hides typos and interrupts.
    • Log anything you choose to ignore.
    • except Exception is broad; bare except is broader.
  13. When does a finally block run?

    The strongest answer: On the way out of the try either way — after a successful run, after a handled or unhandled exception, and on return or break — which is why it is where cleanup goes

    Why it works: Right. That guarantee is the whole point of the construct.

    • finally always runs on the way out.
    • except handles, else runs on success.
    • Cleanup belongs in finally, or in a with block.
    • It runs on return and break too.
  14. You inherit Spark jobs written in Scala. What Scala basics do you need to read them confidently?

    The strongest answer: val versus var (immutable by default), case classes as typed records, Option instead of null, and map, filter and flatMap on collections — the same functional shape as the DataFrame API, compiled to the JVM

    Why it works: Exactly — those four ideas cover most pipeline code, and knowing it runs in the JVM explains where Scala still wins.

    • val, case classes, Option, collections.
    • Immutable by default.
    • Same functional shape as DataFrames.
    • Runs on the JVM.