Interview guides · Amperecloud GmbH
Amperecloud GmbH Software Engineer Interview Guide
Amperecloud GmbH Software Engineer interview guide: Mutual Discovery, take-home assignment and feedback discussion, with prep guidance and a 7-day plan.
The 60-second brief
Amperecloud GmbH builds software for solar and wind energy assets, covering the performance, monitoring and management of those assets. Candidates describe the Software Engineer role as sitting between technical infrastructure and the interfaces people use: backend services, data-driven monitoring tools, and integrating new data sources coming from energy assets. The work runs across the whole development lifecycle, from gathering requirements through design and implementation to analysing the software after it ships, with code reviews and refinement of architectural patterns alongside other engineers.
This guide is for Software Engineer candidates preparing for the Amperecloud GmbH loop. It covers the three reported rounds (Mutual Discovery, Take-Home Assignment, Feedback Discussion), the reported experience questions about past projects, architecture, maintainability, technology choices and handling code feedback, and a seven-day plan. It makes no claim about pay, team structure or tools beyond what candidates describe.
Candidates report three rounds spread over roughly three to five weeks: Mutual Discovery, a Take-Home Assignment, and a Feedback Discussion in which the take-home is reviewed. The reported questions are about your past work and decisions rather than algorithm puzzles.
Focus areas
- Project-based Take-Home Assignments
- Technical Problem Solving
- Software Engineering (Role Responsibilities)
Overview
The loop is short and centres on one piece of work. After an introductory conversation, the middle stage is a task built around the role and meant to resemble the work itself. The last stage, as candidates describe it, is a conversation where you go back through the submitted assignment with the reviewers and talk about the choices you made and what you would change. That shape changes how you prepare: the take-home is not finished when you submit it, because you will be asked to explain and criticise it. Treat the written explanation that travels with your code as part of the deliverable.
Every reported question sits under one heading, Technical and Professional Experience. They ask about the most significant technical challenges in previous projects, the architectural choices in your last major project, keeping code maintainable and scalable in a fast-paced environment, the technologies or frameworks you are most comfortable with and why, and how you handle feedback on code or design. None of these has a single right answer, so what you can bring is specifics: named components, a real constraint, a rejected alternative, and a result you can state.
The role description candidates give includes debugging performance bottlenecks, integrating new data sources from energy assets, and working with product managers and field engineers. Requirements candidates describe list a modern programming language relevant to the stack, version control and an understanding of software architecture, with cloud infrastructure, data-intensive applications and interest in renewable energy as extras. No specific language or framework is reported, so choose the language you can defend best for the take-home and be ready to say why.
Reported topic areas also include requirements understanding and field system support (operational readiness). For the take-home that suggests two habits. First, write down what you assumed wherever the brief was ambiguous. Second, make failure visible in the way a field-device system needs: a check that flags an asset which has sent no data for N intervals, a health endpoint, and a metric or alert on ingest lag, with a short note on how to run and verify it. Logging alone does not show that anyone would notice a silent device. Both habits give the feedback discussion something concrete to talk about.
The Amperecloud GmbH interview process
Candidates describe three stages, each building on the previous one. The questions below are reported as experience questions and are not tied to a specific round, so prepare all of them for the conversations at both ends of the loop.
Mutual Discovery
Mutual Discovery is the opening stage. Candidates report that it is where you hear about the company's mission and the scope of the role, and where you share your background and interests. Treat it as two-way. Arrive with a short account of your career that points at one or two projects you can discuss in depth, because the later stages revisit your past decisions. Also arrive with questions about the role, such as which data sources are integrated, which language the assignment expects and how it is submitted.
What to demonstrate
- Introduction to the company's mission and the scope of the role, as candidates report it
- Sharing your background and interests with the people you meet
How to prepare
- Write a short career narrative that names two projects, your specific contribution to each, and one decision you would defend, so your background is easy to follow
- Write five questions about the role, including what the take-home covers and what language or stack it expects
- Have answers ready for the reported questions on significant technical challenges and on the technologies you are most comfortable with, each with a reason for the choice
- Learn the basics of energy asset monitoring: inverter and SCADA telemetry sent at fixed intervals (for example every 5 or 15 minutes), energy yield against expected yield (performance ratio, irradiance), availability, curtailment, and how a device that stops reporting shows up in monitoring
Take-Home Assignment
Candidates describe the Take-Home Assignment as a task built around the role, meant to resemble the work itself. The reported topic areas include project-based take-homes, technical problem solving and requirements understanding. Because the next round reviews your design decisions, the submission should explain itself: a README that states the problem as you understood it, the assumptions you made where the brief was open, the structure you chose and why, what you left out, and how to run the tests. Use version control with readable commits so your sequence of decisions can be followed.
What to demonstrate
- Working, readable and testable code for a realistic task
- How you break an ambiguous requirement into smaller engineering tasks
- Whether your decisions and trade-offs are visible in what you hand in
How to prepare
- Practise one small end-to-end exercise that ingests time-stamped readings from a source, validates them and exposes a summary, with tests and a README that records assumptions
- Rehearse the redelivered-batch and idempotency-fingerprint practice questions as examples of the reliability issues that appear when integrating a new data source
- Keep a running decision log while you build, one line per choice with the alternative you rejected
- Add a staleness check, a health endpoint and a note on how you would find out something had stopped reporting, since operational readiness is a reported topic area
Feedback Discussion
In the Feedback Discussion, candidates say you go back through your submitted assignment with the reviewers and talk about the choices you made and what you would change. Prepare by reviewing your own submission as if someone else had written it: find the weakest module, the missing test, the assumption that could be wrong, and decide what you would change with more time. Be ready to explain choices such as a library versus custom code, and to hear criticism without defending every line.
What to demonstrate
- How you justify technical choices and acknowledge their trade-offs
- How you respond to critique of your own work
- Whether you can identify improvements without being prompted
How to prepare
- Write a list of the three changes you would make to your submission and the order you would make them in
- Prepare to defend one build-versus-buy decision in the take-home with the cost you accepted
- Rehearse the answer on handling feedback on code or design using a real review where you changed your mind and one where you did not
- Practise explaining a design choice in plain language, since candidates describe working with product managers and field engineers
Where candidates lose points
Submitting the take-home as bare code with no explanation of the choices behind it
The next round reviews your design decisions, so put a README alongside the code: the problem as you understood it, assumptions where the brief was open, why you chose this structure, what you cut, and how to run the tests.
Answering the question about technologies you are most comfortable with with a list of tools and no reasons
Pick two or three, state a concrete project where each was the right fit, and name one situation where you would not use it. The question asks why, so every item needs a reason and a limit.
Describing project outcomes as what the team did without saying what you personally decided or built
For each project in your walkthrough, say I chose, I built or I measured, and attach the result. Keep team credit separate from your own contribution, and be ready for a follow-up on any decision you describe.
Reacting defensively when the take-home is critiqued, or being unable to name any flaw in your own submission
Review your submission before the discussion and list the weakest parts yourself. When a gap is pointed out, acknowledge it, explain what constraint led to it, and say how you would fix it.
Starting to build before settling the requirements, then hiding the guesses you made
Read the brief for what it leaves open and write your assumptions at the top of the README. If one open assumption would change the design substantially, note it explicitly there, and raise it during the discovery conversation if you get the chance.
Questions & practice
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Collapse a redelivered event batch into per-aggregate high-water marks
Easy · hashing · at-least-once · aggregation
You drain a batch of up to 5,000,000 events, each (aggregate_id BIGINT, aggregate_version INT, event_type, payload). The log guarantees order within one aggregate only; the batch merges 64 partitions, and a relay failover has redelivered a range, so an older version for an aggregate can appear after a newer one. Given a map of last_applied_version per aggregate, produce the events worth applying, at most one per (aggregate_id, version), plus the count discarded. Target O(n) time. State the memory for 2,000,000 distinct aggregates and what you do when it does not fit.
Approach
- One pass, one hash map from aggregate_id to the highest version kept, and a discard counter. An event whose version is at or below last_applied_version for its aggregate is dropped without further work, which is the whole reason the event carries its version rather than a delta. O(n) expected time, O(d) space in distinct aggregates.
- Keep the maximum, never the last occurrence. The redelivered range means the final appearance of an aggregate in the batch can be an older version than one seen earlier in the same batch, so last-wins applies stale state over newer state and the projection regresses with no error anywhere.
- Cost the memory instead of calling it large: an 8-byte key plus a 4-byte version is 12 bytes of payload, and an open-addressed table held at a 0.7 load factor costs roughly 17 bytes per entry before per-slot metadata, so 2,000,000 aggregates is tens of megabytes in a native layout and several times that in a runtime that boxes both key and value.
- If the distinct set exceeds memory, partition on hash(aggregate_id) mod P and reduce each partition independently. Every event for one aggregate hashes to the same partition, so the per-partition result is exact and the merge is concatenation rather than a second reduction.
Follow-up
- The payload is a patch rather than a snapshot, so applying only the highest version loses the intermediate changes. What changes in your reduction?
- How do you detect that version 7 arrived while version 6 was never delivered, and what should the consumer do about the gap?
Canonicalise a request body into a stable idempotency fingerprint
Medium · parsing · canonicalisation · hashing
idempotency_key.request_fingerprint is a SHA-256 over the method, path and canonicalised body, and a retry whose fingerprint differs must be rejected with 422 rather than served the stored response. Write the canonicaliser. Bodies are JSON up to 256 KB nested at most 32 levels; clients vary key order, whitespace and unicode escaping, and some send 64-bit ids as JSON numbers. Produce a deterministic byte string such that semantically identical bodies match and any semantic difference does not. State your complexity and name two normalisations you refuse to perform.
Approach
- Parse once into a tree, then re-serialise under fixed rules: object keys sorted, array order preserved, one escaping convention, no insignificant whitespace. Parsing is O(n) and sorting keys is O(k log k) per object, so O(n log n) overall with O(depth) stack, and the 32-level cap is enforced during parsing because hostile nesting is how a canonicaliser becomes a stack overflow.
- Sort keys by their UTF-8 bytes and say why the obvious implementation is wrong in some runtimes: a default string comparison that orders by UTF-16 code units places surrogate pairs, meaning code points from U+10000 up, below U+E000 to U+FFFF, which is not UTF-8 byte order, so two services written in different languages disagree on the same document.
- Do not re-encode numbers through a double. IEEE-754 binary64 represents integers exactly only up to 2^53, so normalising a 19-digit id through a float changes it, and 1 against 1.0 cannot be reconciled without deciding whether they are the same value. Preserve the literal token, and require ids as strings at the API boundary if you want them comparable.
- Reject duplicate keys rather than picking one. JSON permits them and parsers disagree, most keeping the last, so any choice you make ties the fingerprint to a parser detail that the code handling the request does not necessarily share.
Follow-up
- A client sends the same logical request with an extra field your API ignores. Same key, different fingerprint, so you return 422. Is that the right answer?
- Where does the fingerprint get computed relative to request decompression and the body-size limit?
Stop tag and share joins from fanning out a page
Medium · join fan-out · semi-joins · many-to-many
resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.
Approach
- Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
- Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
- Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
- Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.
Follow-up
- The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
- A resource can be shared with the same user twice under different permissions. Does your count change, and should it?
Find version gaps and relay lag with window functions
Medium · window functions · event tables · frame semantics
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
Follow-up
- Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
- Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?
This category focuses on your career narrative, testing your ability to explain your technical decisions and p
Medium · Technical and Professional Experience
This category focuses on your career narrative, testing your ability to explain your technical decisions and project outcomes.
Approach
- Structure the career narrative as two or three projects in order, each built around one decision you owned: the context, the options on the table, what you chose, and the measured result (for example 'p95 latency went from 800 ms to 200 ms' or 'nightly job failures dropped from weekly to none'). Use your real numbers, not round ones.
- For every decision, name the alternative you rejected and the constraint that ruled it out: deadline, team size, data volume, operational burden or cost. A decision with no alternative is only a description of what happened.
- Keep your contribution separate from the team's in plain first person: 'I designed the ingestion schema and the retry logic; a colleague built the dashboard.'
- Include one outcome that went badly and what you changed afterwards. Candidates report that a later stage is a review of their own take-home and its possible improvements, so practising honest self-critique here carries over.
- End each project with the lesson you now apply by default, such as 'I add idempotency keys before I add retries', so the story leads into how you would approach the take-home.
Follow-up
- Which of those decisions would you reverse today, and what would reversing it cost?
- How did you measure that outcome, and what was the baseline before your change?
- What did the option you rejected have going for it?
What were the most significant technical challenges you faced in your previous projects?
Medium · Technical and Professional Experience
Approach
- Choose the challenge closest to this role: telemetry from unreliable devices or sources, time-series volume that broke the simple approach, or a backfill of historical sensor or meter data. If you have none of these, pick the nearest equivalent, such as a flaky third-party integration or a high-volume event pipeline, and say why it is comparable. State in one sentence why it was hard.
- Walk through the diagnosis in order: the symptom, your first hypothesis, the measurement that confirmed or ruled it out (profiler output, EXPLAIN ANALYZE on the slow query, logs joined by a correlation id), then the fix. Hypotheses you discarded are worth mentioning because they show your method.
- Quantify the impact before and after (error rate, latency, throughput, hours of manual work) and the blast radius: who was affected and what could have broken.
- Say what made it hard beyond the code, such as a bug that only reproduced in production, unclear ownership or missing observability, and how you worked around that.
- Finish with the guard that stops it coming back: a regression test, an alert on the leading metric, a runbook entry or a design change that removes the failure mode.
Follow-up
- How did you reproduce it, and what did you do while you still could not?
- Which hypothesis did you drop first, and what evidence ruled it out?
- What check would have caught this before it reached production?
Can you walk us through the architectural choices you made in your last major software project?
Medium · Technical and Professional Experience
Approach
- Open with the system's purpose and the constraints that shaped it (request or event volume, latency target, consistency needs, team size), then sketch the components: clients, services, data stores, queues and external integrations, with the write path and the read path traced separately.
- Pick two or three decisions and give each its trade-off. Monolith or services: independent deploys against operational overhead and network failure modes. Relational or time-series/document store: joins and constraints against write throughput and schema flexibility. Synchronous calls or a queue: simple error handling against decoupling, which brings retries and therefore a need for idempotent consumers.
- Explain where failures are handled: timeouts, retries with exponential backoff and jitter, idempotency keys, dead-letter queues, and what a user sees when a dependency is down.
- Say which choices you made and which you inherited, and for an inherited one whether you would make it again.
- Name what you would change now and the signal that showed it: a hot table, slow deploys, a coupling that blocked a feature. The same habit helps when the take-home is reviewed.
Follow-up
- Which component fails first at ten times the load, and which metric would tell you?
- What happens to in-flight data if the downstream store is unavailable for an hour?
- If you rebuilt it today with the same team, what would you do differently?
How do you ensure your code is maintainable and scalable when working in a fast-paced environment?
Medium · Technical and Professional Experience
Approach
- Treat maintainability and scalability as separate things. Maintainability: clear module boundaries, dependencies passed in so logic can be tested in isolation, consistent naming, short decision records. Scalability: stateless services, pagination instead of unbounded queries, indexes that match real query patterns, and measuring before optimising.
- Name the mechanics you rely on when speed matters: small pull requests with review, unit tests for pure logic and integration tests at the database and API boundaries, and CI that runs linters and type checks (for example mypy or tsc) on every merge.
- Show how you make shortcuts explicit instead of hidden: a feature flag, a ticket with the condition that triggers the follow-up (for example 'when the table passes a million rows'), and holding off on an abstraction until a third use case appears.
- Give one concrete example with evidence: a test that caught a regression before release, or a refactor that measurably cut change time or incident count.
- Connect it to the take-home: a README that states assumptions, tests on the core logic, and a short list of what you would do with more time, which gives you material for the feedback discussion.
Follow-up
- Tell me about a shortcut you took that you later had to pay back. What did it cost?
- When time is short, how do you decide what gets a test?
- What would you change first in your last codebase if usage grew tenfold?
What specific technologies or frameworks are you most comfortable with, and why?
Medium · Technical and Professional Experience
Approach
- Name two or three technologies you have shipped production code with, and back each with depth: a non-default behaviour you relied on or debugged. Examples: in standard CPython builds the GIL stops threads running Python bytecode in parallel, so CPU-bound work goes to multiprocessing and I/O-bound work to threads or asyncio; PostgreSQL MVCC leaves dead tuples that VACUUM has to reclaim.
- Give the 'why' as a trade-off, not a preference: ecosystem maturity, static typing, performance profile, operational familiarity, hiring pool. Then name a case where you would choose something else.
- Mention a limitation you hit in production and how you worked around it, since that separates having used a tool from understanding it.
- Be honest about the difference between tools you have used and tools you are comfortable with, and describe how you get up to speed on an unfamiliar stack: read the core docs, build a small spike, read well-maintained open-source code that uses it.
- Candidates report a take-home built around the role, so for that task pick the stack whose choices you can defend line by line in a review.
Follow-up
- When would you not choose that framework?
- What surprised you about it once it ran in production?
- How did you become productive the last time you had to learn a new stack quickly?
One log partition stops advancing while the others drain
Easy · poison message · head-of-line blocking · consumer lag
Search results for a subset of tenants are hours stale; the rest are current. The projection consumer reports lag of zero on 15 of 16 partitions and 400,000 on one. Its error rate is flat and its CPU is idle. outbox_event has no pending rows older than a second, so the relay has published everything it holds. Identify the mechanism, give the ordered checks, and state what you do in the first ten minutes versus what you change permanently.
Approach
- Read the lag distribution first. A slow consumer lags everywhere; zero on fifteen partitions and 400,000 on one is not throughput. Idle CPU on the stuck partition means the consumer is not advancing its offset at all, which points at one message it cannot get past rather than at a rate problem.
- Exonerate the producer before touching the consumer. No pending outbox rows older than a second means the relay published, so the event exists in the log. This separates never sent from sent and never applied, which are different code paths and usually different owners.
- Read the message at the stuck offset and the handler's log lines for its event_id. A flat error rate with no progress has two explanations and you must distinguish them: the handler is throwing and the retry loop is swallowing it, or the handler is blocking on something and never returning. Idle CPU with no error lines favours the second.
- Mitigate before diagnosing further. Move the offending event to a dead-letter store and commit the offset past it. Adding consumers does nothing here, because a partition is consumed by exactly one member of the group, and the blast radius is every aggregate hashed to that partition, not only the aggregate that produced the bad event.
Follow-up
- The dead-lettered event carried aggregate_version 7 and the projection had applied 6. What must the replay do differently if 8 and 9 landed in the meantime?
- How do you show staleness to the user while the partition is behind, given the API already returns the projection's watermark?
Suggested practice checklist
Seven days built around the three reported stages: project stories for the conversations, a small end-to-end build with documentation for the take-home, and a self-review for the feedback discussion.
Day 1 — Map the loop and inventory your projects
- Write the three reported stages in order and one sentence each on what you will bring to it
- List four past projects, and for each write your role, one design decision, one problem you hit and the measurable result
- Mark the project you can discuss in the most depth, since it will carry the architecture and challenge questions
Deliverable: A one-page loop map plus a four-project inventory with role, decision, problem and result for each.
Day 2 — Challenges and pivots
- Write out two stories for the question about the most significant technical challenges, choosing the ones closest to device telemetry, time-series volume or historical backfills, and include one where the first approach failed and you changed course
- For each story, record the symptom, how you diagnosed it, the options you weighed and the result
- Say the career-narrative framing aloud and cut it until it points at a specific project
Deliverable: Two written challenge stories with diagnosis steps and results, and a spoken career narrative.
Day 3 — Architecture and maintainability
- Sketch the architecture of your last major project: components, data flow, storage and external dependencies
- Write down two choices you made, the alternative you rejected for each, and what would break first under heavy growth
- Prepare the maintainability answer with three concrete practices from your own work, such as tests, review habits and module boundaries
Deliverable: An architecture sketch with two decisions and rejected alternatives, and a maintainability answer with three named practices.
Day 4 — Technology fluency and build versus buy
- Choose the language and framework you would use for the take-home and write why, with one situation where you would pick differently
- Prepare a build-versus-library decision from your experience, with the cost you accepted
- Write down which parts of version control, cloud infrastructure and data-intensive work you have hands-on experience in
Deliverable: A written technology-choice answer, a build-versus-library example, and a hands-on experience list.
Day 5 — Build a mini take-home
- Build a small energy telemetry service that ingests per-inverter power readings from a simulated source, validates them, handles repeated deliveries and exposes a summary aggregated into time buckets
- Cover the issues this data really has: readings that arrive late or out of order, gaps from devices that go offline, UTC and DST handling of timestamps, and unit consistency (kW versus kWh)
- Add a staleness check that flags an asset with no data for N intervals, a health endpoint, and a metric or alert on ingest lag, then write a README with assumptions, structure, omissions and run instructions
- Use the redelivered-batch and idempotency-fingerprint practice exercises to check how your handling of repeated records compares
Deliverable: A working mini project with tests, README, decision log, staleness check and health endpoint, in a repository with readable history.
Day 6 — Rehearse the feedback discussion
- Review your Day 5 project as a reviewer: write three problems and the changes you would make in order
- Answer the feedback question aloud using two real review stories, one accepted and one disputed with evidence
- Practise the deadline-and-debt and breaking-callers behavioral exercises as rehearsal for explaining trade-offs
- Have a friend criticise your README and note where you got defensive
Deliverable: A self-review with three prioritised improvements, a recorded feedback answer, and notes on the practice exercises.
Day 7 — Full run and stakeholder explanation
- Explain one technical decision from your Day 5 project to a non-technical listener in plain language, without jargon
- Run the whole sequence aloud: introduction, one project walkthrough, take-home explanation, critique response
- Write the questions you will ask about the role, the assignment and the data you would work with
Deliverable: A completed run-through, a plain-language explanation of one decision, and a final list of questions to ask.
Behavioural preparation
The reported questions are about your experience: challenges, architecture, maintainability, technology choices and how you handle critique. Candidates also describe the role as involving cross-functional work with product managers and field engineers, so be ready to explain technical decisions to people outside engineering. Stories should show a decision you owned, the reasoning, the alternative you rejected and how you responded when others disagreed.
- Describe a technical challenge where your first approach failed and how you pivoted.
- How do you decide between building a custom solution versus using an existing library or service?
- What were the most significant technical challenges you faced in your previous projects?
- Describe a time you explained a technical trade-off to a product manager or someone outside engineering, and what they decided afterwards.
- Tell me about a design you would change if you built it again, and what you would do differently.
How do you handle feedback on your code or technical design from peers or senior engineers?
Medium · Technical and Professional Experience
Approach
- Choose a specific code review or design review where the feedback changed something concrete, and name the artifact: a pull request, a design note, an interface. A story with no artifact sounds rehearsed.
- Structure it as situation, the feedback you received, your first reaction, what you did to evaluate it, and the outcome. Spend most of the time on the evaluation step: you reproduced the issue, wrote a failing test, ran a benchmark, or asked the reviewer for the scenario behind the comment.
- Show both outcomes. Describe feedback you accepted and what changed, then a case where you disagreed, brought evidence or an alternative, and either persuaded the reviewer or conceded. Make clear the decision rested on facts.
- Close with how it changed your habits, for example smaller pull requests, a design note before large changes, or a checklist you now run before requesting review. This links directly to the feedback discussion after the take-home.
- Avoid two failures: the story where the reviewer was simply wrong and you were right, and the generic answer that you welcome feedback with no example attached.
Follow-up
- Tell me about feedback you disagreed with. What did you do and what happened?
- How do you give feedback to a more senior engineer whose design you think is flawed?
- What is one piece of review feedback you received that you still apply today?
Ship under a deadline and bound the debt you chose
Medium · pagination · technical debt · tradeoffs
You have four days to ship a tenant-facing listing endpoint. The version you would defend uses keyset pagination over (tenant_id, status, updated_at DESC, resource_id DESC); the version you can finish uses LIMIT/OFFSET with no matching index. Describe a deadline call you actually made of this shape: what you shipped, what you knowingly deferred, how you bounded the damage with a mechanism rather than an intention, and the specific numeric condition that would force the follow-up. Name who you told and where you wrote it down.
Approach
- Name the deferred failure precisely instead of calling it slow. OFFSET n makes the database produce and discard n rows, so cost grows with page depth; without an index matching the sort, every matching row is read and sorted before the limit applies; and rows inserted between two page fetches shift across the boundary so items are skipped or repeated with nothing in the response to signal it.
- Bound the blast radius with something mechanical rather than a promise: cap maximum page depth, cap page size, restrict the endpoint to one internal caller, or keep it behind a flag. State which failure each cap removes and which it leaves standing.
- Attach a number to the trigger and wire it to an alarm: the first tenant crossing N resources, or the endpoint's p99 crossing its share of the 400 ms budget, so the debt announces itself instead of waiting to be remembered.
- Write it where the next engineer looks, which is the code and the ticket, not a chat message: what was deferred, why, the cap, and the trigger.
Follow-up
- At what page depth does the offset version breach your latency budget, given your page size and row counts?
- What breaks first when you switch to keyset pagination later, and what does a client holding an old page token see?
Tell callers you do not own that their integration breaks
Medium · deprecation · compatibility · stakeholders
A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.
Approach
- Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
- Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
- Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
- Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.
Follow-up
- How would you detect a consumer that reads the field only during a monthly export?
- One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?