As a Data Scientist at Zalando, you are at the intersection of large-scale e-commerce operations, complex algorithmic challenges, and consumer-facing product strategy. Zalando operates as a massive platform where data-driven decision-making is not just a support function; it is the engine that powers personalized fashion recommendations, supply chain optimization, and marketplace health. Your role is to transform raw, high-volume data into actionable insights that directly influence how millions of customers discover and purchase fashion.
You will typically work within cross-functional squads alongside software engineers, product managers, and business stakeholders. The work is highly collaborative and requires you to balance technical rigor with business pragmatism. Whether you are designing experiments to test new features or diagnosing sudden drops in key performance indicators, your ability to communicate complex findings to non-technical stakeholders is just as critical as your ability to write efficient SQL or build predictive models. Expect an environment that values innovation and evidence-based reasoning.
Zalando places a high premium on product-sense. Even in highly technical roles, you must be able to justify your model or analysis by its impact on the customer experience and the bottom line.
Initial Conversation
reportedBecause the format is not fixed, prepare the reasoning rather than the ritual. Nearly every version of this round draws on the same underlying material: a design you can defend, a metric you can define exactly, an analysis whose assumptions you can state out loud. Only the wrapper changes, whether that is a take-home, a live case, a deep dive on past work, or a rough estimate on a whiteboard. Answers rehearsed to fit one shape stall the moment the shape differs. Practise naming the assumption behind a number, then saying how much the conclusion moves if that assumption is wrong.
What to demonstrate
- Whether your justification for a method survives the question 'why not the simpler thing', including when the simpler thing would have worked
- Precision under pressure: what exactly counts as an active user, a conversion or a success, over what window, with what exclusions
- Whether you carry an argument through to a recommendation instead of stopping at a list of tradeoffs
How to prepare
- For each project you plan to mention, write the metric definition in one sentence: numerator, denominator, time window, exclusions. Say it out loud once, because vagueness shows up in speech before it shows up on paper.
- Rehearse the same project at three lengths: two minutes, ten minutes, and a deep dive on one technical decision. Cutting live is harder than it sounds.
- For your headline result, write down what would have had to be true for it to be wrong, and how you ruled that out.
Technical Assessment
reportedMuch of what gets scored here happens out loud while you type. Nobody can see your reasoning inside a half-written query, so five silent minutes read as being stuck even when they are not. State the plan in plain language first: which tables, what grain you are aggregating to, and the one filter that defines the population. Then write it. The narration doubles as insurance, because a wrong plan gets caught early and cheaply while a wrong query gets caught at the end with no time left to redo it. A timed statistics section, where one exists, is a separate test with its own clock.
What to demonstrate
- Whether the query you write matches the plan you just described
- What you do with a hint, meaning whether the correction gets absorbed or the first approach gets defended
- Whether you can debug your own wrong output by reading the result set and naming which part of the query produced the anomaly
How to prepare
- Solve three problems while screen-sharing into a recording, then watch it back and mark every stretch longer than thirty seconds where you said nothing
- Practise compressing the plan into one sentence before typing, then check afterwards whether the finished query actually matched it
- Time yourself on statistics questions that carry a business reading, such as what a confidence interval does and does not claim, rather than re-reading notes without a clock
Onsite Interview
reportedA day of back-to-back interviews samples your floor, not your ceiling. Four hours in, the habits that carry a good answer are the first to go: restating the question before solving it, asking what the data would have to look like, checking a number before quoting it. What the day decides is whether the tired version of you is still someone to leave alone with an ambiguous problem. The round that sinks a candidate is usually not the hardest one. It is the one immediately after the round that went badly.
What to demonstrate
- Whether the late rounds get the same clarifying questions as the first one, or whether you start answering immediately to save effort
- Whether a weak answer stays in the room it happened in, instead of following you into the next conversation as apology or distraction
- Whether the quality of your questions holds up, since fatigue removes curiosity about the problem before it removes knowledge of the method
How to prepare
- Rehearse the length, not just the content: book four mock interviews of different types in one afternoon with short gaps, because the one you need to observe is the fourth
- Put the two or three questions you ask at the start of any problem on a card in front of you, so that under fatigue it is a habit you run rather than a decision you make
- Decide in advance what the gap between rooms is for: water, one line of notes on anything you promised to follow up, and an explicit close on the round that just ended so it does not travel
- Prepare a different closing question for each interviewer, so the end of a long day does not produce the same one four times
2 candidate reports. Individual accounts describe a particular role and hiring cycle.
Zalando Software Engineer interview: technical depth, system design, and manager debrief
After a recruiter screen, I had an HR call about my experience, salary expectations, and a detailed explanation of the process and interviewers. I then had several technical rounds in a structured but flexible sequence: general technical depth, system design, and behavioral or managerial evaluation. Each technical round lasted about an hour. The questions tied closely to work I had described, inc…
Read full experienceZalando Software Engineer Interview Experience: Mismatch on level and pay
The process soured quickly. I went through complex rounds and was rejected for a mid-level role. To me, the expectations did not match what had been communicated: they seemed to want more experience while offering less pay. The interviewer interactions felt rude and unfriendly, which made it hard to stay motivated. I left upset, with the sense that the company was not acting in good faith about f…
Read full experiencePracHub editorial advice for the preparation topics above.
Year-over-year and week-over-week comparisons on calendar dates
Retail demand is organised around a promotional calendar and a 4-5-4 fiscal calendar, not around the Gregorian one, so aligning on calendar date puts a peak promotional week against an ordinary week whenever the event moves, and the occasional 53-week fiscal year shifts every subsequent week by one. Moving holidays compound this across markets. Compare on fiscal week with the event aligned (week-before-event, event week, week-after), state which alignment you used, and treat any single-week comparison spanning a major event as uninterpretable without that alignment.
Comparing average order value across a change that alters basket composition
Free-shipping thresholds, multi-buy offers and minimum-spend coupons make customers bunch their basket just above a cut-off, so average order value moves because the mix of orders changed, not because anyone is spending more per item. The same trap appears when a change suppresses small orders: average order value rises while total revenue falls. Decompose into units per order and price per unit, look at the whole basket-value distribution rather than the mean (the bunching is visible as a spike at the threshold), and evaluate the change on revenue per session rather than on the conditional mean of orders that happened.
Explaining an aggregate move without decomposing the mix shift
Split the change in the aggregate into within-segment movement and movement in segment weights before you explain it. Every segment's rate can fall while the overall rate rises, purely because volume shifted toward segments that already had higher rates.
Ignoring interference between units in a marketplace experiment
Ask whether one unit's treatment can change another unit's outcome through shared inventory, a matching pool, a social graph or a common budget. Where it can, randomise at a level that contains the spillover, such as region or time slice, and say explicitly what that costs you in statistical power.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Net revenue per order without fanning out return rows
You get two DataFrames. order_lines has order_line_id, order_id, customer_id, quantity, unit_paid_price_cents, line_status, delivered_at_utc. return_lines has return_line_id, order_line_id, quantity_returned, refund_amount_cents, initiated_at_utc. One order line can have several return rows. Produce one row per order_id with gross_cents over delivered lines only, refund_cents from returns initiated within 90 days of that line's delivery, net_cents, and units_kept. The output must have exactly one row per order_id, and gross_cents must equal a total you compute without ever touching return_lines.
Approach
- Filter order_lines to line_status == 'delivered' first, and compute gross_cents = quantity * unit_paid_price_cents per line. Keep that per-line frame as the control total before any merge.
- Aggregate return_lines to one row per order_line_id with groupby('order_line_id').agg(sum of quantity_returned, sum of refund_amount_cents, min of initiated_at_utc) so the right side of the join is unique on the join key.
- Because the 90-day window is per return row, not per order line, apply the window before aggregating: join initiated_at_utc against the parent line's delivered_at_utc, keep rows inside the window, then aggregate. Doing it after aggregation loses the individual initiation dates.
- Left merge the aggregated returns onto the delivered lines with validate='m:1' so pandas raises instead of silently duplicating, then fillna(0) on the refund and quantity columns.
- Group to order_id, sum gross_cents, refund_cents, and units_kept = quantity - quantity_returned, and assert the gross total equals the control total computed in step one.
Follow-up
- The order line was paid in a non-USD currency with fx_rate_to_usd on the line. Where does the conversion belong, and what breaks if you convert after aggregating to order grain?
- Refunds arrive weeks after the order. Which date do you attribute refund_cents to when you report a monthly net revenue series, and why does the answer change how long you must hold the report back?
- How would the query change if a partial return could be refunded in two instalments under the same rma_id?
Correct a demand estimate for stockout-censored sales history
daily has 180 rows for one SKU at one node: snapshot_date, gross_units_sold, minutes_unavailable (0 to 1440), was_listed (bool), unit_paid_price_cents and is_promo. Recorded sales are the minimum of demand and what was sellable. Estimate the uncensored daily demand rate treating the available fraction of the day as exposure, then estimate the units lost to unavailability over the 180 days. Write the estimator yourself rather than calling a GLM library, and state the assumption the correction rests on.
Approach
- Define exposure_d = was_listed * (1440 - minutes_unavailable) / 1440, in day-equivalents. A day with zero exposure had nothing sellable, so its gross_units_sold is 0, and it contributes 0 to both the numerator and the denominator of the closed-form rate below: it cannot move the estimate in either direction. Drop those days from the fit anyway, because the equivalent log-exposure offset form is undefined at log(0) and any GLM you later swap in will fail on them. Keep every one of them in the lost-units sum, where they carry the largest term.
- Model sales as Poisson with mean lambda * exposure_d. For an intercept-only model with a log-exposure offset the maximum likelihood estimate has a closed form, lambda_hat = sum(sales) / sum(exposure), so no optimiser is needed; write that and derive it from the score equation rather than asserting it.
- Estimate lost units as sum over all 180 days of lambda_hat * (1 - exposure_d), which is the demand the model says arrived while nothing was sellable. Convert to lost revenue only at the unit price actually in force on those days, and mark it as an upper bound.
- State the assumption plainly: demand arrives at a constant rate within the day. It is wrong in the direction that matters, because a SKU that sells out does so after the day's demand has already concentrated in the earlier hours, so scaling by the available fraction understates true demand. End-of-day snapshots cannot distinguish a sellout at 09:00 from one at 23:00.
- Extend to lambda_d = exp(b0 + b1is_promo + b2log(price)) * exposure_d if promo days are a material share, fitting by Newton or by iteratively reweighted least squares, and check the sign of the price coefficient for plausibility before trusting the lost-units figure.
Worked solution 40 min
- Build exposure, then report how many day-equivalents of exposure the 180 calendar days actually contain; if it is 150, a sixth of the history is unobservable.
- Compute naive_rate = gross_units_sold.mean() over all 180 days and lambda_hat = gross_units_sold[exposure > 0].sum() / exposure[exposure > 0].sum(), then assert that dropping the exposure > 0 filter reproduces lambda_hat bit for bit, which is the statement that zero-exposure days carry no information about the rate.
- Compute lost_units = (lambda_hat * (1 - exposure)).sum() over all 180 days, including the zero-exposure ones.
- Bootstrap over days (resample the 180 day-rows with replacement, recompute lambda_hat) for a 95 percent interval on both the rate and the lost-unit figure.
- Sanity-check by recomputing with minutes_unavailable forced to zero, which must return exactly the naive mean.
Follow-up
- Some of the lost demand moved to a substitute SKU or a different node. How would you bound the share that was genuinely lost rather than displaced?
- You only have end-of-day snapshots. What would you instrument to make this estimate honest, and at what data volume?
- The forecast that drove the buy was fitted on this same censored history. Walk through what happens over three consecutive seasons if nobody corrects it.
Sessionise a raw event stream with a thirty-minute inactivity rule
events has anonymous_id, customer_id (nullable, populated only after sign-in), event_ts_utc, event_type in {page_view, product_view, search, add_to_cart, checkout_start, order_placed}, order_id (non-null only on order_placed). Rows arrive unsorted and timestamps can tie. Build the session table: session_id, anonymous_id, customer_id (last non-null in the session), started_at_utc, ended_at_utc, counts per event type, and order_id. A gap of strictly more than 30 minutes from the previous event of the same anonymous_id opens a new session. Do not use groupby.apply or a Python loop over rows.
Approach
- Sort by (anonymous_id, event_ts_utc) with a stable tie-break on a secondary key such as event_id, so that tied timestamps produce a deterministic ordering and the result is reproducible across runs.
- Compute gap = groupby('anonymous_id')['event_ts_utc'].diff(). A new session starts where gap is null (first event for that visitor) or gap > 30 minutes; take the cumulative sum of that boolean to get a session ordinal, which is the vectorised equivalent of a gaps-and-islands query.
- Build session_id as the pair (anonymous_id, ordinal) rather than a global running integer, so re-running on a different date partition does not renumber existing sessions.
- Aggregate once with a single groupby on the session key: min and max of the timestamp, value_counts of event_type pivoted to columns, and order_id taken as the max of a column that is null everywhere except order_placed.
- Key on anonymous_id, not customer_id, because sign-in happens mid-session and keying on the identity would split one visit into a signed-out session and a signed-in one. Carry customer_id forward as the last non-null value instead.
Follow-up
- Two visits 29 minutes apart are one session under this rule, and a visit resumed the next morning is two. What breaks if you instead cap sessions at a fixed wall-clock length?
- The same person browses on mobile web and then buys on the app. How would you stitch those, and what does the stitching do to a conversion-rate denominator?
- How do you handle events that arrive late, after the session containing them has already been written?
Can you explain the difference between a self-join and a window functi…
Can you explain the difference between a self-join and a window function in terms of performance?
Approach
- Say which table is the grain you start from, and join outward from it.
- State the window function and its partition and ordering out loud before writing it.
- Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
Follow-up
- How does the query change if the join becomes one-to-many?
- How would you verify this result without re-running the same query?
Given a table of user logs, how would you find the time difference bet…
Given a table of user logs, how would you find the time difference between the first and second purchase for each user?
Approach
- Say which table is the grain you start from, and join outward from it.
- State the window function and its partition and ordering out loud before writing it.
- Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
Follow-up
- What breaks if events arrive late or out of order?
- How does the query change if the join becomes one-to-many?
Reconcile converting sessions against order lines with a null-safe anti-join
fct_session records order_id for the order placed inside a session; fct_order_line carries session_id, which is NULL for store, call centre and denied-consent orders. For last week, list sessions with is_bot_flagged = FALSE and order_id IS NOT NULL that have no row in fct_order_line with the matching session_id, and count them by entry_channel, device_type and tracking_consent. Use fct_session (session_id, started_at_utc, ended_at_utc, entry_channel, device_type, order_id, tracking_consent, is_bot_flagged) and fct_order_line (order_line_id, order_id, session_id, placed_at_utc).
Approach
- Before writing the join, count NULLs in the probe key: SELECT COUNT(*) FROM fct_order_line WHERE session_id IS NULL. That single number decides which anti-join forms are safe.
- Write the anti-join as NOT EXISTS correlated on session_id, or as a LEFT JOIN with WHERE ol.session_id IS NULL. Both are unaffected by NULLs in the probe side; NOT IN is not.
- Bound the probe side by placed_at_utc over the same week plus a buffer, since an order can be placed minutes after the session row is closed and a tight window manufactures false orphans.
- Group the survivors by tracking_consent first, because denied-consent orders are expected orphans by design and should be separated from genuine loss before anyone opens a ticket.
- Cross-check the residual against fct_session.order_id joined to fct_order_line.order_id: an orphan on session_id but a match on order_id is a tracking defect, not a missing order.
Worked solution 15 min
- Run the NULL count on fct_order_line.session_id and record it.
- Write the NOT IN version, observe zero rows, then write the NOT EXISTS version and observe the real set.
- Add WHERE session_id IS NOT NULL to the NOT IN subquery and confirm it now agrees with NOT EXISTS.
- Group the NOT EXISTS result by tracking_consent, entry_channel and device_type.
Follow-up
- Should denied-consent sessions be in this reconciliation at all, given they can never join, and what do you report instead for them?
- The orphan count triples on one device_type overnight. What are the two or three hypotheses you test first?
If you notice a sudden drop in our "add-to-cart" rate, how would you g…
If you notice a sudden drop in our "add-to-cart" rate, how would you go about diagnosing the root cause?
Approach
- Fix the population and the time window before naming any metric.
- State what result would change your recommendation, so the answer is falsifiable.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- Which segment would you cut first, and what would that rule out?
- How would you detect that the metric is being gamed rather than genuinely improving?
How would you design a metric to measure the success of a new personal…
How would you design a metric to measure the success of a new personalized recommendation feature?
Approach
- State what result would change your recommendation, so the answer is falsifiable.
- Decompose the metric into the rates that drive it, and say which one you would check first.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- How would you detect that the metric is being gamed rather than genuinely improving?
- What would you do if the primary metric and the guardrail moved in opposite directions?
How do you balance short-term conversion gains against long-term user …
How do you balance short-term conversion gains against long-term user retention?
Approach
- Decompose the metric into the rates that drive it, and say which one you would check first.
- Restate the decision this analysis has to support, and who acts on the answer.
- Name one primary metric, then the guardrail that stops it being gamed.
Follow-up
- Which segment would you cut first, and what would that rule out?
- What would you do if the primary metric and the guardrail moved in opposite directions?
How would you evaluate the impact of a new checkout UI change?
How would you evaluate the impact of a new checkout UI change?
Approach
- Fix the population and the time window before naming any metric.
- Name one primary metric, then the guardrail that stops it being gamed.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- How would you detect that the metric is being gamed rather than genuinely improving?
- What would you do if the primary metric and the guardrail moved in opposite directions?
Walk me through the steps of designing an A/B test for a new landing p…
Walk me through the steps of designing an A/B test for a new landing page.
Approach
- Decide the analysis before seeing data, including how long it runs and when you look.
- Name the randomisation unit first; it decides the variance and what the test can detect.
- State the primary metric and the minimum effect worth shipping, then size the test.
Follow-up
- What would you conclude if the result is positive but the test is underpowered?
- How would you handle interference between treated and control units?
What are the most common experimentation pitfalls you have encountered…
What are the most common experimentation pitfalls you have encountered?
Approach
- Name the guardrails that would stop a launch even on a positive primary result.
- State the primary metric and the minimum effect worth shipping, then size the test.
- Decide the analysis before seeing data, including how long it runs and when you look.
Follow-up
- How would you handle interference between treated and control units?
- What would you conclude if the result is positive but the test is underpowered?
Score a monthly promotional event on incremental margin
Merchandising runs a sitewide weekend event every month. The current readout is event-week revenue against the prior week. You have fct_order_line (promotion_id, line_discount_cents, discount_funding, unit_paid_price_cents, unit_cost_cents, placed_at_utc, sku_id), fct_return_line and dim_sku. Deliverable: design the scorecard. Name the primary metric, the two effects the primary must be corrected for, how you charge the discount cost, and what has to be held back for the measurement to mean anything.
Approach
- Reject the week-over-week baseline outright. The event is scheduled onto a week chosen because demand was expected to be high, and it is compared against the week its own pull-forward drained. Both halves of the comparison are contaminated in the same direction.
- Set the primary as incremental contribution margin per exposed customer against a holdout, measured over a window extending past the event by at least one category repurchase interval, so pull-forward appears as a post-period deficit rather than being booked as lift.
- Correct for the second effect, cannibalisation, by reading at category level rather than promoted-SKU level. A discount on one SKU pulls units off its full-price neighbours, and a SKU-level read counts that transfer as creation.
- Charge the discount correctly. The depth is paid to every buyer, including those who would have paid full price, so incremental margin equals incremental units times unit margin at the promoted price, minus baseline units times discount per unit. Split by discount_funding, because supplier-funded depth does not consume retailer margin and should not be charged to the event.
- Name the holdback. A randomised customer holdout, or a geo split with pre-period parallelism checked. If the event genuinely cannot be withheld from anyone, say so and fall back to a synthetic control on comparable markets, and state plainly that the estimate is now assumption-dependent rather than measured.
- Net returns before reporting. Promoted units return at different rates than full-price units, so attribute refunds to the parent order's placed date and hold the read until the return window closes.
Worked solution 30 min
- Define the holdout and verify pre-period parallelism on category revenue per customer for at least eight weeks before the event.
- Compute units and net merchandise revenue by arm for the event window and for a post-period of one category repurchase interval, refund-adjusted to placed date.
- Compute contribution margin by arm using unit_paid_price_cents less unit_cost_cents, less shipping and return costs, plus recovered_value_cents.
- Split SUM(line_discount_cents) by discount_funding and charge only the retailer-funded and shared portions to the event.
- Report event-window incremental margin and post-period incremental margin separately, then the sum, so pull-forward is visible rather than netted silently.
Follow-up
- The event lifts revenue 22 percent and incremental contribution margin is negative. Walk me through exactly how that arithmetic works.
- Supplier funding covers 60 percent of the depth. Does the event still lose money, and does that change what you recommend?
- How long does the post-period window need to be, and what data did you use to choose that length?
A published cohort number moved with no product change
The 90-day repeat rate for a first-delivery cohort published four weeks ago now reads 29%; the published snapshot says 22%. No product, pricing or lifecycle-email change landed in that window. You have the metric query in version control, the published snapshot table, and the underlying fct_order_line and fct_return_line. Deliverable: determine whether cohort membership changed, the metric definition changed, or the underlying data was restated, and say what you would do to the historical series before anyone reads a trend from it.
Approach
- Separate code from data with one experiment: run the version of the query that produced the published number against today's data, and run today's query against the same data. If old code on new data reproduces 22%, the data is stable and the definition moved; if new code on old data reproduces 29%, same conclusion from the other side.
- Diff cohort membership as a count, not a rate. Cohort membership is fixed at first delivery, so the denominator should be immutable. If it moved, someone re-indexed the cohort on signup or on order placement, or started excluding customers whose first order was later cancelled or fully returned.
- Diff the numerator rule line by line: does a second order have to contain a delivered, not-fully-returned line, or does any placed order count; is the 90-day clock measured from first delivery or from first order; is the window inclusive at the boundary.
- Test for restatement: compare row counts and terminal line_status values for the cohort's order lines then versus now. Late-arriving deliveries and backfilled refunds legitimately move both sides and are not a bug, but they mean the number was published before it was stable.
- Whichever it is, restate the entire history on one definition and annotate the change date on the chart. A series with a step change where the definition moved is worse than no series, because it reads as a result.
Follow-up
- If it turns out to be a restatement rather than a definition change, what publication lag would have prevented it, and what does that lag cost in decision speed?
- How would you make the definition self-documenting so the next person cannot silently change it?
- Someone has already briefed the 29% as a retention win. How do you correct that without the correction itself reading as a result?
For someone who has spent the last year in notebooks, dashboards or modelling work and has not written raw SQL under time pressure. The first four days rebuild query fluency against a fixture you control and can verify by hand; the last three attach that fluency to the rest of the loop.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Build a fixture you can check answers against
- Create a local Postgres or SQLite database with four tables (users, sessions, events, orders) holding roughly 200 rows you generated yourself, so you know the contents well enough to predict every result.
- Deliberately seed the cases that break queries: a user with no sessions, a session with no events, two orders sharing a timestamp, a NULL in one join key, and one duplicated user row.
- Before writing any SQL, hand-compute five answers on paper (how many users placed at least one order, median orders per ordering user, and three others) and save them as the ground truth for the week.
Deliverable: A one-command seed script plus a text file of five hand-computed answers to grade every later query against.
Practice prompt ↗Practice prompt ↗Practice prompt ↗Worked solution ↗02Joins, filters and NULL semantics
- Answer "which users have no orders" three ways (LEFT JOIN with IS NULL, NOT EXISTS, NOT IN) and confirm that the NOT IN version returns zero rows once the subquery contains a NULL, because the comparison is never TRUE.
- Reproduce the LEFT JOIN that silently collapses to an inner join by putting a right-table predicate in WHERE, then fix it by moving the predicate into the ON clause, and record both row counts.
- Create a fan-out bug on purpose by joining orders to order_items and summing the order total, then correct it with a pre-aggregated subquery and explain in one line which table changed the grain.
Deliverable: One annotated .sql file holding the three join traps, each with the wrong result and the corrected result side by side.
Practice prompt ↗Practice prompt ↗Practice prompt ↗03Window functions and frames
- Write three window queries against the fixture: a running order total per user, the rank of each order within its user by value, and the day gap to that user's previous order, then check each against the day-one ground truth.
- Run ROW_NUMBER, RANK and DENSE_RANK over a column containing ties, print all three side by side, and write one sentence on when each is the correct choice.
- Switch one query from the default frame (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which is what you get when ORDER BY is present and no frame is written) to ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, and explain why the output differs only when the ORDER BY column has duplicates.
Deliverable: Three verified window queries plus a short note explaining the RANGE versus ROWS difference in your own words.
Practice prompt ↗Practice prompt ↗Practice prompt ↗04The four analytical query patterns
- Write a monthly retention grid: first order month per user, then months-since-first as the column, and verify that month zero equals the cohort size exactly.
- Sessionize the events table under a 30-minute inactivity rule using LAG plus a cumulative sum over a new-session flag.
- Build a four-step funnel that counts distinct users rather than events at each step, and state the rule you applied to a user who reaches step three without ever logging step two.
Deliverable: One file with the retention, sessionization and funnel patterns, each carrying a one-line note on the assumption it bakes in.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Write SQL the way you will have to write it live
- Set a 12-minute timer and solve three medium prompts in a plain editor with no execution and no autocomplete, then run them and tally syntax errors separately from logic errors.
- Narrate one solution aloud while writing it, stating the grain of each intermediate result (one row per user, one row per user-day) before you type its body.
- Rewrite your slowest solution as a CTE chain where every CTE name states its grain, and time yourself re-solving it from blank.
Deliverable: A recording of one narrated solution plus an error tally that separates syntax from logic.
Practice prompt ↗Practice prompt ↗06One day for everything that is not SQL
- Write the preconditions of the two-sample t-test from memory, then check them: independent observations, and a difference in means whose sampling distribution is approximately normal, which at large sample sizes follows from the central limit theorem rather than from normality of the raw values.
- Write the difference between an odds ratio from logistic regression and a relative risk, and state the condition under which the two are close (low outcome prevalence).
- Prepare a 90-second answer to "how would you know this model is any good" that names the metric, the baseline you would beat, and the cost of the errors you care about.
Deliverable: One page of notes covering test preconditions, the odds-ratio caveat and the model-quality answer.
Practice prompt ↗Practice prompt ↗07Full loop rehearsal
- Run a 45-minute mock with someone willing to interrupt: 20 minutes of SQL, 15 minutes defining a metric, 10 minutes on a past project.
- Re-solve from blank the two queries you were slowest on this week and compare the times against day five.
- Write a five-line answer to "walk me through a project" that puts a number in the first sentence and names the decision the work changed.
Deliverable: Mock feedback notes plus a timed project narrative you can deliver without reading it.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Have two ready. In one, the data was on your side and you had to move someone who outranked you. In the other, the pushback was correct and you changed position. The second is the harder story and it lands better, because it shows you separate being right from being attached to an answer. Name the person's actual objection.
Tell me about a time you had to explain a complex technical finding to…
Tell me about a time you had to explain a complex technical finding to a non-technical stakeholder.
Approach
- Quantify the outcome, including what you would not claim credit for.
- Pick a story where you drove the decision, not one where you observed it.
- Close with what you would do differently, concretely.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that project again?
Choose between three teams asking for the same week
Three requests land on the same Monday. Merchandising wants a size-curve read before a buy deadline on Thursday. Growth wants a channel attribution rebuild that has been requested twice and dropped twice. Supply chain wants a stockout root-cause on a category that cancelled four thousand units last month. You have one week and no help, and each requester believes theirs is first. State what you do, in what order, and what you tell the two people who do not get the week.
Approach
- Sort by the decision behind each request rather than by who sent it. A buy deadline is an irreversible commitment with a fixed date; an attribution rebuild changes no decision this week.
- Ask each requester two questions: which decision changes, and what happens if the answer lands a week later. Those two separate a real deadline from felt urgency without arguing about either.
- Look for the cheap partial before assuming any request consumes the week. If the size curve already exists at style grain, the merchandising read may be two hours rather than four days.
- Decline explicitly with a start date attached instead of leaving a request in a silent queue. Growth has been dropped twice, so a third silent drop is a relationship cost you are choosing to pay; name it rather than incur it by default.
- Escalate the collision upward once, with the three decisions and their dates side by side, so the tradeoff is resolved where it is owned rather than by whoever follows up hardest.
Follow-up
- Growth escalates to your manager saying analytics never supports them. What did you do before that happened, and what do you do now?
- The buy deadline moves to Tuesday. What do you cut from the size-curve read, and what do you refuse to cut?
Scope a one-line request for our best customers
A merchandising director messages you: can you pull our best customers. There is no other context. You have dim_customer, fct_order_line and fct_return_line. Best could mean highest net spend, highest contribution margin, most frequent, most recent, lowest return rate or highest expected future value, and the resulting lists differ enormously. You get one clarifying exchange before the director is in meetings for the rest of the day. Write what you send back, and describe how you proceed if no reply comes.
Approach
- Ask about the decision, not the definition. What are you going to do with the list narrows six candidate metrics faster than asking which of six metrics they meant.
- Put a stated default in the same message so a non-reply is still progress: trailing 365 days, customer grain, net of returns, and say so in one clause.
- Make the ambiguity concrete with a measured number rather than a menu. One line reporting the overlap between the top decile by net revenue and the top decile by contribution margin forces the choice without a meeting.
- Name the filters that depend on the use rather than on the metric. A mailing list needs email_consent = TRUE and excludes account_status in ('closed','fraud_blocked'); a buy-planning read wants neither filter.
- If nothing comes back, ship the default with the definition, window and return treatment written at the top of the output, and attach the alternative cut so the conversation continues on an artefact.
Follow-up
- The director replies that it is for a loyalty upgrade offer. What changes in the query?
- How do you stop this becoming six different best-customer lists across the company?
- 01
Tell me about a time you had to explain a complex technical finding to a non-technical stakeholder.
- 02
Three requests land on the same Monday. Merchandising wants a size-curve read before a buy deadline on Thursday. Growth wants a channel attribution rebuild that has been requested twice and dropped twice. Supply chain wants a stockout root-cause on a category that cancelled four thousand units last month. You have one week and no help, and each requester believes theirs is first. State what you do, in what order, and what you tell the two people who do not get the week.
- 03
A merchandising director messages you: can you pull our best customers. There is no other context. You have dim_customer, fct_order_line and fct_return_line. Best could mean highest net spend, highest contribution margin, most frequent, most recent, lowest return rate or highest expected future value, and the resulting lists differ enormously. You get one clarifying exchange before the director is in meetings for the rest of the day. Write what you send back, and describe how you proceed if no reply comes.
Is this an official Zalando interview guide?
No. It is PracHub's own research and practice material for the Data Scientist role at Zalando. Rounds and questions reflect what candidates have reported, not a process Zalando has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How long should I prepare for the interviews?
Depending on your current level of familiarity with SQL and A/B testing, plan for at least 2–4 weeks of focused practice.
PracHub interview research ↗Does the interview process vary by location?
While the core technical assessment remains consistent, the team you interview with—whether in Berlin, Zurich, or elsewhere—may focus more on specific product areas like logistics, personalization, or marketing.
PracHub interview research ↗Is the coding interview strictly algorithmic?
At Zalando, the focus is more on data manipulation and SQL than on complex data structures or LeetCode-style algorithm puzzles.
PracHub interview research ↗How much weight is placed on cultural fit?
Significant. Zalando values team-oriented individuals who can communicate effectively and contribute to a collaborative, inclusive environment.
PracHub interview research ↗Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
platform · Accessed 2026-09-22 - 02PracHub Data Scientist practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-22