Candidate-facing descriptions of the Software Engineer role at Ziphq focus on designing and implementing backend services and features, working in existing and sometimes complex codebases, and solving algorithmic problems. The role also covers code reviews, contributing to architectural decisions, and working with product managers and other engineers across the development lifecycle, from design through deployment and post-deployment monitoring.
The stated requirements are proficiency in at least one major language (Python, Java and Go are the examples given), a strong grasp of data structures, and experience with version control such as Git. Cloud infrastructure such as AWS, familiarity with distributed systems, and open-source contributions are listed as nice to have. Reported interview topics are Python, data structures, algorithms, coding problem solving and system design.
For preparation, the role breaks into two kinds of work. The first is algorithmic: n-ary trees, graphs and shortest paths, dynamic programming, backtracking, and optimising a brute-force approach to O(n log n) or better. The second is practical engineering in a provided codebase: adding an API endpoint, extending a feature to handle a new edge case, debugging a failing test, and designing classes for a domain model. This guide covers both, plus the project deep-dive and the behavioral questions candidates report.
Recruiter Screen
reportedThe recruiter screen is a conversation about your background and your fit for the role. Use it to settle the practical questions that can end a process late: work authorisation and whether you need sponsorship, your timeline and any competing deadlines, location, and compensation expectations. Raise sponsorship here rather than later. This is also the time to ask what the technical assessments involve, which platform and languages the online assessment supports, and whether a later round involves work in a provided codebase. That way your preparation fits the loop you will actually face.
What to demonstrate
- Whether your background fits the role, told as a short summary of what you have built and in which language
- Whether hard constraints such as sponsorship, start date and location come up now rather than at offer stage
- Whether you can describe one recent project clearly enough to set up the later deep-dive discussions
How to prepare
- Write a short background summary that ends with the kind of backend or feature work you want to do next
- List your constraints (authorisation, sponsorship, start date, competing timelines) in one line each and state them as facts
- Prepare questions about the format of the technical assessments, the environment they run in, and which languages are allowed
Technical Assessments
reportedCandidates describe a series of technical assessments that get harder as they go and include online assessments. An Industry Coding Assessment on CodeSignal is also mentioned. Treat any online assessment as a formal interview. Set up and test your environment beforehand, read the whole prompt and its input bounds before you write code, and aim to pass every test case rather than polishing one part. The question bank's class-modelling material is good practice for this stage: a domain model such as a payment processor or notification system, an in-memory store with expiry and history, a vending machine. So is standard data-structure work. For spec-driven tasks, get a correct version passing the given tests before you extend or optimise it. Keep the code organised so that the next requirement is an addition rather than a rewrite.
What to demonstrate
- Whether your solution passes the provided tests, including edge cases, and not only the sample input
- Whether your classes, methods and data structures let later requirements be added without restructuring
- Whether the complexity of your algorithm fits the stated input size
How to prepare
- Build an in-memory key-value store with per-key expiry and a history of values, adding one requirement at a time and noting which changes forced a rewrite
- Model a payment processor or notification system as classes with clear responsibilities, and write the tests before extending it
- Complete at least two full practice assessments in a browser-based editor using only your language's standard library, then review which tests failed and why
Live Coding Sessions
reportedLive coding sessions are interactive: you solve problems in real time while an interviewer follows along. The reported coding questions for this role make good practice for these sessions. They cover n-ary tree traversal, graph traversal and shortest-path search, dynamic programming, backtracking, and optimising a brute-force approach to O(n log n) or better. Get the basics right. Ask clarifying questions about edge cases and constraints before coding, narrate your logic as you write, and dry-run a sample input before you call the solution finished. Some interviewers stay quiet. If yours does, keep narrating and ask for feedback when you are stuck instead of reading the silence as a verdict. When the output is wrong, trace the smallest failing input by hand before you edit anything.
What to demonstrate
- Whether you clarify inputs, constraints and edge cases before writing code
- Whether you explain your approach and its Big O complexity while you code, not only afterwards
- Whether you respond to hints and change strategy when an approach stalls
- Whether you test the code on a sample input before calling it done
How to prepare
- Write preorder, postorder and level-order traversals of an n-ary tree from memory, both recursively and iteratively, and state when recursion depth becomes a risk
- Solve shortest path on an unbounded grid with obstacles using BFS, and explain how you bound the search space
- Practise in pair-programming style: narrate every decision, and ask your partner to stay silent for part of the session
- Rewrite a brute-force solution to O(n log n) using sorting, a heap or binary search, and name the step that removed the extra factor
Deep-Dive Discussions
reportedDeep-dive discussions are in-depth conversations about your past projects and experiences. Prepare to go below the summary: why you chose a design, the alternatives, what broke, and how it would hold up at larger scale. Prepare with the reported behavioral questions for this role: a challenging project you owned from start to finish, a technical disagreement with a teammate, prioritising under conflicting deadlines, and learning a new technology on the fly. Choose projects where you made the decisions yourself, so that you can answer follow-ups on trade-offs without falling back on what the team decided. Use STAR to keep each story compact, and leave room for the interviewer to steer.
What to demonstrate
- Whether you can explain the reasoning behind decisions on a project you owned, including the alternatives you rejected
- Whether you can say what went wrong on a project and what you changed as a result
- Whether your account of a disagreement shows how it was resolved and what you did personally
How to prepare
- For two projects, write the problem, your role, two key decisions with their alternatives, and a measurable result
- Rehearse each project at a short length and a detailed length, and practise switching between them when interrupted
- Prepare one STAR story each for a technical disagreement, conflicting deadlines, and learning a new technology or domain quickly
2 candidate reports. Individual accounts describe a particular role and hiring cycle.
ZipHQ Software Engineer Interview Experience — A Graph Pursuit Problem in the First Round
The author reports being rejected after a first-round Zip interview focused on a pursuit game in a finite undirected graph. Two players know each other’s locations and can move to an adjacent room or stay still, with the evading player acting first. The question concerned how long capture could take. After the applicant struggled, the interviewer suggested comparing travel distances from the two…
Read full experienceZipHQ Software Engineer Interview Experience — Aced the HM Round, Failed a Simple Tree DP Problem
Overall the difficulty was pretty easy — too bad I failed this tech round. Tech round: a tree problem. Each node has a value. For each node, you can pick either the current node's value, or the values of its children. If you pick the current node, you can't also pick its children — return the maximum value sum. The core of it is really just getValue(root): return max(getValue(root), getVal(root.c…
Read full experiencePracHub editorial advice for the preparation topics above.
Treating the online coding assessment as a warm-up
The source notes mention a CodeSignal Industry Coding Assessment. If your loop includes it, treat it as a formal interview. Check your environment, your language choice and your standard-library fluency beforehand. Read every requirement before you write code, and get a correct, test-passing version in place before you refactor or optimise. A plain solution that passes the tests puts you in a stronger position than a clever one that covers half the requirements. For class-based tasks, keep each responsibility in one place so that a new requirement is an added method rather than a rewrite across files.
Rewriting a provided codebase instead of working inside it
For tasks such as adding an API endpoint to a backend skeleton, extending a feature for a new edge case, or debugging a failing test, start by reading. Find out how existing handlers are structured, where validation lives, how errors are returned and how the tests run. Match those patterns, run the existing tests before and after your change, and add a unit test for the new behaviour. For a failing test, reproduce it and state the input, expected value and actual value before you edit anything, and fix the cause rather than the assertion. One reported practical-coding question asks you to discuss the scalability trade-offs of your chosen implementation, so be ready to say how your change would behave under more load.
Coding in silence during live sessions
Ask about edge cases and constraints before writing, then narrate as you go: the approach, its complexity, and what you are about to check. Dry-run a sample input before you declare the solution finished. If the interviewer is quiet, keep narrating and ask for feedback when you are stuck. A hint helps only if the interviewer can follow your reasoning, and how you change strategy after a hint is part of what they see.
Knowing tree and graph patterns only as recursive templates
The reported questions include n-ary tree traversal, graph traversal, shortest path with dynamic barriers, and backtracking. Be able to write each traversal both recursively and with an explicit stack or queue, because deep inputs can exhaust the recursion limit (CPython's default is 1000 frames). On grid searches, keep a visited set, say how you bound an unbounded grid, and say what changes when barriers move over time. On backtracking, state the branching factor and the pruning rule before you code.
Deep-dive answers that say 'we' and skip the trade-offs
In deep-dive discussions, pick projects where you owned the decisions, and say 'I' when the decision was yours. For each project, have two decisions ready with the alternatives you rejected and what broke. Keep stories in STAR form and end each one with a result you can measure or check.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Add a new API endpoint to a provided backend skeleton.
Add a new API endpoint to a provided backend skeleton.
Approach
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- What is the worst case, and how likely is it on real data?
- How does this change if the input no longer fits in memory?
Refactor a provided code snippet to improve memory efficiency.
Refactor a provided code snippet to improve memory efficiency.
Approach
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
Debug a failing test case within a provided codebase.
Debug a failing test case within a provided codebase.
Approach
- Name the brute-force solution and its complexity before improving on it.
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- What is the worst case, and how likely is it on real data?
- How does this change if the input no longer fits in memory?
Extend an existing feature by handling a new edge case or data input.
Extend an existing feature by handling a new edge case or data input.
Approach
- Walk one small example through your approach before writing the whole thing.
- Choose the data structure from the access pattern, not from familiarity.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Identify the heaviest tenants in a five-minute window under memory pressure
The edge service handles about 3,000 requests per second across roughly 50,000 tenants, peaking near 9,000. Expose the 50 heaviest tenants by request count over the trailing five minutes so limits can be tightened before one tenant's backfill starves the fleet. You may not retain five minutes of raw records. Give the exact solution and its memory, then the bounded-memory approximation with its error stated as a formula, and say which you would ship and at what tenant cardinality that choice changes.
Approach
- Do the exact version first, because it is affordable at this cardinality: a ring of 300 one-second counters per tenant, advanced lazily, is 1,200 bytes of counters per tenant and roughly 60 to 90 MB for 50,000 tenants with overhead. Carry a running total and subtract the bucket you overwrite so a window read is O(1) rather than 300 adds.
- Extract the top 50 with a size-k min-heap over the tenant sums: O(d log k) for d tenants, against O(d log d) to sort them all. Maintaining the heap continuously instead of on query requires a tenant-to-heap-index map, because incrementing a count already inside the heap means sifting from a known position, and without that map you rebuild the heap on every request.
- State the approximation precisely rather than gesturing at sketches. Misra-Gries with m counters retains every item whose true count exceeds N/(m+1), and each retained count underestimates the truth by at most N/(m+1). With m = 1,000 and N = 900,000 requests in the window the error is roughly 900 requests, which is fine for spotting a tenant sending 50,000 and useless for ranking two tenants 200 apart.
- Say what breaks when the window slides: Misra-Gries and Space-Saving are insert-only and cannot be decremented as records age out. The workable construction is one summary per sub-window, say ten seconds, with 30 summaries merged at query time, and the merged error is the sum of the per-summary errors, so the bound degrades linearly in the number of sub-windows.
- Choose and defend it: at 50,000 tenants the exact rings cost under 100 MB in a process that already holds more, so ship exact. Keep the sketch for the case that actually motivates it, a per-principal or per-IP key where cardinality runs to millions and is not bounded by anything you control.
- Raise the fleet problem before it is asked: each of 20 to 40 instances sees only its share, and the top 50 of one shard is not the top 50 of the fleet. Either aggregate counts centrally or accept that a per-instance threshold multiplied by instance count is the limit you are really enforcing.
Worked solution 25 min
- Size the exact structure: 300 one-second counters per tenant across 50,000 tenants, plus the running-total trick that makes a window read O(1).
- Write the top-k extraction with a size-50 min-heap and compare its complexity against sorting all 50,000 sums.
- Substitute N = 900,000 and m = 1,000 into N/(m+1) and state in requests what the sketch can and cannot distinguish.
- Write the sub-window merge for the sliding case and state the resulting bound for 30 merged summaries.
Follow-up
- The heaviest tenant is heavy because of one export job rather than user traffic. Should the limiter treat those as the same tenant?
- Two tenants sit tied at the boundary of the top 50. Does your answer flap, and does the flapping matter?
- You switch to per-principal keys and cardinality goes to 10 million. Walk through what changes.
Keep soft-deleted accounts from blocking re-registration
app_user holds user_id, tenant_id, email CITEXT, password_hash (NULL for SSO principals), email_verified_at, auth_version, status ('invited','active','suspended','deactivated'), created_at, updated_at, deleted_at. Two live accounts for one address inside a tenant must be impossible, but an address freed by a soft delete must be reusable, and the same tenant may delete and re-register it repeatedly. Write the uniqueness DDL for PostgreSQL 16, then the equivalent for MySQL 8 where partial indexes do not exist, and say what each permits once three deleted rows already hold that address.
Approach
- Start from what is actually unique: not (tenant_id, email), but (tenant_id, email) among live rows. PostgreSQL says that directly — CREATE UNIQUE INDEX app_user_live_email ON app_user (tenant_id, email) WHERE deleted_at IS NULL. A full constraint over the same two columns burns the address permanently the first time someone deletes an account.
- Keep case-insensitivity in the type or the index, never in the application: CITEXT as given, or UNIQUE (tenant_id, lower(email)) as an expression index where the extension is unavailable. A case-sensitive unique column is exactly how two accounts for one human appear.
- For MySQL 8 the predicate has to move inside the key: add a discriminator column that is a constant 0 while the row is live and is set to user_id on delete, with UNIQUE (tenant_id, email, deleted_marker). Live rows share the constant and still collide; deleted rows differ from each other and stop colliding.
- State the NULL variant and its dependency: leaving the marker NULL for deleted rows also works, because a unique index treats NULLs as distinct — true in MySQL, and true in PostgreSQL only under the default NULLS DISTINCT, which PostgreSQL 15 lets you reverse. Check the polarity against the three existing deleted rows: constant-on-live is what preserves the collision you want, and reversing it silently admits duplicate live accounts.
- Say what a soft delete must do besides setting deleted_at: increment auth_version so existing tokens stop validating, leave resource.owner_user_id and resource_revision.actor_user_id intact, and accept that the address is retained — erasure is a different requirement answered by scrubbing the column, not by a DELETE that would break those references.
Follow-up
- A deleted account re-registers with the same address the next day. Do the old resource rows follow the new user_id, and how does the API keep the two principals apart?
- How do you honour an erasure request while resource_revision.actor_user_id still references this table?
- What changes if a user may hold membership in two tenants?
Hold a per-tenant active cap against concurrent creates
A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.
Approach
- Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
- Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
- Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
- State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.
- For the job case the invariant is expressible per row, so let the database hold it: a partial unique index on job_run (tenant_id, job_type) WHERE status IN ('queued','running') makes a second running export unwritable and the loser takes 23505, mapped to 409. That is strictly better than a counter — no drift, no reconciliation — and it is available only because the cap is one rather than fifty.
- Add the retry discipline each route demands: under SERIALIZABLE both 40001 and deadlock 40P01 are retryable and the retry must re-execute the read, while under READ COMMITTED with the counter nothing retries, because the conflict is reported to the caller rather than raised as an error.
Worked solution 35 min
- Reproduce with two sessions that both count 49, both insert and both commit, at READ COMMITTED and then at REPEATABLE READ; record the final active count for each.
- Repeat both sessions at SERIALIZABLE and record which SQLSTATE the loser receives and at which statement it is raised.
- Implement the counter form and run a 20-way concurrent create against a tenant sitting at 45 active resources.
- Implement the partial unique index for the job case and race 20 enqueues of the same export.
Follow-up
- A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
- The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?
- How do you detect after the fact that the counter drifted, without locking the table?
Choose what to break when replication lag reaches forty seconds
Reads are served from two replicas: 14k requests/second, about 85% absorbed by cache, so roughly 2.1k reads/second reach the database. Writes go to the primary at 1.2k/second. A tenant's backfill drives replication lag from under 100 ms to 40 seconds and it is still climbing. Sessions that have just written are pinned to the primary. Decide, endpoint class by endpoint class, whether to serve stale, fail, or route to the primary, and justify each choice with the load it adds to the primary. Then state what you would have built beforehand.
Approach
- Establish blast radius before cause, because mitigation and diagnosis have different deadlines. The decisive arithmetic is what happens if the database reads move to the primary: 2.1k reads/second on top of 1.2k writes/second roughly triples its operation count, on the node already absorbing the backfill that caused this. Reads and writes are not equal in cost, so treat that as an argument against a blanket move rather than as a capacity model - but it is enough to rule out routing everything to the primary.
- Classify endpoints by what staleness costs, not by how important they feel. Reads whose staleness is invisible - listings, search, counters - stay on the replica and return the watermark so the client can tell. Reads that immediately follow that same session's write keep their primary pin, which is a small bounded slice of traffic rather than the whole 2.1k/second. Reads that feed a decision with a side effect - authorisation, quota, the read half of a read-modify-write - must not be stale at all, because a 40-second-old permission row is the stale-permission failure wearing a different costume; those go to the primary or fail.
- Shed instead of queueing. If the must-be-fresh class alone exceeds the primary's headroom, refuse its lowest-value slice with 503 and a retry-after. A request queued behind a saturated primary holds a connection for a client that has already given up, and the retry storm that follows is what turns degradation into an outage. Bound the connection pool per role so the read fallback cannot consume the write path's connections - that bulkhead is the single decision that determines whether writes survive the next ten minutes.
- Attack the cause in parallel, since it is the one thing that can be stopped. The backfill is the load generator. A backfill that reads replication lag as its throttle signal and pauses above a threshold would have made this a non-event, with batch sizes small enough that each batch's write volume is a fraction of what a replica can apply per second. That is most of the answer to what should have existed beforehand.
- Name the mechanism you would prefer over session pinning. Capture the write position at commit and require the read path to be at or past it: compare the primary's pg_current_wal_lsn() at commit time against the replica's pg_last_wal_replay_lsn(), and fall back to the primary only for the specific request that is ahead of the replica. Session pinning is the cheap approximation and it over-pins - every read in the window goes to the primary whether or not it needed to, which is a share of the cost being paid right now.
Worked solution 35 min
- List the endpoints in three buckets - staleness invisible, staleness visible to the writer only, staleness unsafe - and attach the share of the 2.1k reads/second each bucket carries.
- Compute the primary's operation count under each routing option and mark which options are arithmetically available.
- Write the pin rule and its window, then the shed rule: which slice, what status code, what retry-after.
- Write the backfill's throttle predicate against a measured lag value, including its pause threshold and resume condition.
Follow-up
- Lag returns to normal in nine minutes. Which mitigation do you remove first, and which one stays permanently?
- A user reports their change did not save, and the write committed. Trace the path that produces that report and name the signal that would have shown it before the report arrived.
- The replica is 40 seconds behind but otherwise healthy. Do you take it out of rotation? What does that do to the other replica's lag?
Evolve the resource contract without breaking integrations you cannot upgrade
GET /v1/resources/{id} returns status from a four-value enum ('draft','active','archived','deleted'), a numeric version, and the body inline. Consumers are a browser app you deploy and roughly 300 server-side integrations, some untouched for two years, that switch exhaustively on status and parse ids as JSON numbers. You must add a 'pending_review' status, move bodies over 256 KB to a body_ref pointer, and expose per-field change history from resource_revision. Specify the compatibility policy, the wire changes, how both generations are served, and the evidence that lets you remove the old shape.
Approach
- Write the policy first and date it: fields are added, never retyped or repurposed; consumers ignore unknown fields; an unknown enum value maps to a documented fallback; nothing is removed until telemetry shows no caller reads it. Then say the uncomfortable part out loud - v1 shipped without the unknown-value rule, so 300 running integrations have no fallback, and no server change can install one into code that is already deployed.
- That single fact forces per-request negotiation rather than a server-side default. Keep one internal model and select a serialiser from an explicit version in the request, and default a caller that sends nothing to the oldest supported version. Defaulting to the newest is the change that breaks every integration that never asked for anything, on the day you ship.
- Downgrade 'pending_review' for old callers to the nearest state they already handle, 'draft', and state the loss explicitly: those integrations cannot see review state and will treat the resource as editable. If that is unacceptable for one integration, the remedy is moving it to the new version, not a cleverer projection - there is no mapping that invents a state the client has no code for.
- Make the body change additive. Old callers keep
bodyinline; the new shape addsbody_refand a size field, and resources over the limit are served to old callers by resolving the pointer server-side or by refusing with a documented code, chosen once and published. Never repurposebodyto carry the pointer: a client that renders it shows a storage key to a user, and that failure is silent, where a missing field would have been loud. While you are here, serialise BIGINT ids as strings in the new shape - a browser parsing JSON numbers gets IEEE-754 doubles, exact only to 2^53 - and treat that as its own breaking change requiring the same negotiation, not a quiet fix. - Add change history as a separate sub-resource, GET /v1/resources/{id}/revisions, keyset-paginated over (resource_id, version) rather than as an array inside the resource. A field added to a hot response is paid for by every caller including those that never read it, and an unbounded array inside a cached object destroys the size assumptions the cache was configured with.
- Retire on evidence rather than on a date alone: count requests per negotiated version per credential, publish a Sunset header (RFC 8594) with the removal date and a link to the migration, contact the credentials still on the old version, then answer 410 Gone once it is removed. Keep each version's serialiser under snapshot tests so a refactor cannot change v1's bytes by accident.
Follow-up
- An old integration submits a status transition while the resource is really in 'pending_review'. What does the write path accept, and what does it reject?
- Two years on you want to delete the v1 serialiser. What evidence makes that safe, and who must be contacted before it happens?
- How would you test against a two-year-old integration rather than against today's source?
One log partition stops advancing while the others drain
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.
- Fix permanently by bounding handler attempts and dead-lettering on exhaustion, so no single message can stop a partition. Then replay the dead-lettered event once the handler is fixed: it carries aggregate_id and aggregate_version, so a consumer that discards versions it has already applied can absorb the replay, and resource_revision is the fallback if the event itself is unusable.
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?
- What changes if the message is poison because a previous deploy wrote a payload shape the current code cannot parse?
For someone fluent in a dynamic language who has shipped real work but has never had to say what the runtime is doing underneath. The week is built on measuring and deliberately breaking things, because the questions that expose this background are the ones where the interviewer asks why a second time.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Map the loop and prepare the recruiter screen
- Write a short background summary, plus one line each for work authorisation or sponsorship, start date, location and competing timelines, and plan to raise sponsorship in the recruiter screen.
- Pick one interview language and commit to it. List the standard-library calls you look up most (sorting with a comparator, heaps, deques, default dictionaries, string split and join) and drill them until you no longer need to look them up.
- Write the questions you will ask the recruiter about the technical assessments: platform, allowed languages, and whether a later round uses a provided codebase.
Deliverable: A one-page screen sheet: background summary, constraints, language choice and questions for the recruiter.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Trees: n-ary traversal and tree DP
- Implement preorder, postorder and level-order traversal of an n-ary tree, each one recursively and with an explicit stack or queue, and test on an empty tree, a single node and a deep chain.
- Solve maximum non-adjacent sum on an n-ary tree. For each node, return two values in one post-order pass: include = the node's value plus the sum of every child's exclude value, and exclude = the sum, over all children, of the larger of each child's two values. The answer is the larger of the root's two values, in O(n) time.
- Given parent-child ID pairs, find the root IDs (nodes that never appear as a child) and the path from a root to any node using a parent map.
Deliverable: Six traversal functions and two tree problems, each with its complexity written above the code and the edge-case inputs used to test it.
Practice prompt ↗Practice prompt ↗03Graphs, shortest paths, DP and backtracking
- Solve shortest path on an infinite grid with obstacles using BFS. Explain how you bound the search (for example, a one-cell margin around the bounding box of obstacles, start and target) and what changes when barriers appear or move over time.
- Work one medium-to-hard dynamic programming problem. Write the state, the transition and the base case in words before any code, then cut memory to rolling rows if each row depends only on the previous one.
- Write one backtracking solution with pruning and state its branching factor and maximum depth.
- Pair with someone on one graph traversal problem and narrate the whole time, as practice for the reported pair-programming format.
Deliverable: Three solved problems (grid shortest path, DP, backtracking) with the search bound or DP state written out, plus notes from the paired session on where you went quiet.
Practice prompt ↗Practice prompt ↗04Object-oriented design for the online assessment
- Build an in-memory key-value store in stages: set, get and delete first, then per-key expiry checked against a supplied timestamp, then value history that can be queried by time. Write the tests for each stage before its code.
- Model a vending machine or a payment processor as classes. Name each class's responsibility, the state it owns and the errors it raises. Then add one new requirement and note whether it forced changes outside a single class.
- Complete one full practice assessment in a browser-based editor, and pass every provided test before you refactor.
Deliverable: A staged key-value store with tests for each stage, one domain model with a written list of class responsibilities, and a record of which practice-assessment tests failed and why.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Practical work in a provided codebase
- In a small backend project you did not write, add an endpoint that follows the existing routing, validation and error-handling patterns, with a unit test for the success case and one failure case.
- Extend an existing function to handle a new edge case or input type. Write a test that fails before your change and passes after it.
- Break a test on purpose, then debug it as you would in the interview: state the input, expected value and actual value before editing, and fix the root cause rather than the assertion.
- Work through the debugging drill on a log partition that stops advancing, writing your ordered checks before you read the approach.
Deliverable: One review-ready change in an unfamiliar codebase (new endpoint, extended feature, fixed test), each part with its own test.
Practice prompt ↗Practice prompt ↗06Optimisation, heaps and scalability trade-offs
- Take a brute-force O(n^2) solution, such as a pair search or an interval overlap check, and bring it to O(n log n) with sorting, a heap or binary search. Write down which step removed the extra factor.
- Refactor a snippet for memory: replace a materialised list with a streaming pass, or a full DP table with rolling rows, and measure peak memory before and after.
- Complete the worked coding exercise on finding the heaviest tenants: size-k min-heap extraction and the memory cost of the exact structure.
- Complete the worked design exercise on replication lag, then practise stating the scalability trade-off of your chosen approach in two or three sentences.
Deliverable: Two optimised solutions with before-and-after complexity, one measured memory refactor, and written answers to both worked exercises.
Practice prompt ↗Practice prompt ↗07Deep-dive and behavioral rehearsal, then a mock session
- For two projects you owned, write the problem, your role, two decisions with the alternatives you rejected, and the result. Rehearse each at a short length and a detailed length.
- Prepare STAR stories for a challenging project you owned end to end, a technical disagreement (including one across teams), prioritising conflicting deadlines, and learning a new technology or domain quickly.
- Run a mock live-coding session on a tree or graph problem with a partner who stays mostly quiet. Narrate, dry-run a sample input, and ask for feedback when you are stuck.
- Explain the worked SQL exercise on holding a per-tenant cap under concurrent creates out loud, as practice for describing a race and its fix.
Deliverable: Two project write-ups, four STAR stories, and notes from the mock session on where you paused or skipped the dry run.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Deep-dive discussions cover your past projects and experiences. The reported behavioral questions for this role focus on ownership, disagreement, prioritisation and learning quickly. Use STAR to keep each story compact, choose stories where you made the call, and be ready to explain the technical trade-offs behind the project as well as its outcome.
Describe a challenging project you owned from start to finish.
Describe a challenging project you owned from start to finish.
Approach
- Name the disagreement and how you resolved it with evidence.
- Give the blast radius: what could have broken, and what you measured.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
How do you prioritize tasks when faced with conflicting deadlines?
How do you prioritize tasks when faced with conflicting deadlines?
Approach
- Close with what you would do differently, concretely.
- Pick a story where you made the decision, not one where you watched it.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
Ship under a deadline and bound the debt you chose
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.
- Report what actually happened in your real example, including the case where the trigger never fired and the debt was correctly never repaid.
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?
- Who would have overruled you if you had asked for two more days, and did you ask?
- 01
Describe a challenging project you owned from start to finish.
- 02
Tell me about a time you had a technical disagreement with a teammate and how you resolved it.
- 03
Describe a technical conflict between two teams and how you moved it to a decision.
- 04
How do you prioritize tasks when faced with conflicting deadlines?
- 05
Describe a situation where you had to learn a new technology on the fly to solve a problem.
- 06
Tell me about entering an unfamiliar domain under time pressure and how you became productive.
Is this an official Ziphq interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Ziphq. The rounds and questions reflect what candidates have reported, not a process Ziphq has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗What stages does the Ziphq Software Engineer process include?
Candidates report four stages over roughly three to five weeks: a recruiter screen, technical assessments that get harder as they go and include online assessments, live coding sessions, and deep-dive discussions about past projects and experiences. Treat this as a reported pattern and confirm the current format with your recruiter.
PracHub Software Engineer practice ↗How long should I spend preparing for the coding rounds?
It depends on how recently you have practised. Instead of counting weeks, check yourself against the reported topics. Can you write n-ary tree traversals, a grid BFS, a DP with a clearly defined state and a backtracking search from memory? Can you model a small domain in classes, with tests? Focus on patterns rather than memorised solutions, and use the seven-day plan in this guide to find your gaps.
PracHub interview research ↗What if I get stuck during a live coding interview?
Keep talking. Say what you have tried, what you think is wrong, and what you would check next. An interviewer can give a useful hint only if they can follow your reasoning, and how you respond to a hint and change strategy is part of the conversation. If the interviewer is quiet, do not read the silence as a verdict. Keep narrating and ask for feedback.
PracHub interview research ↗Is the "Industry Coding Assessment" on CodeSignal a hard barrier?
The source notes describe it as a significant step. If it is part of your loop, treat it as a formal interview: set up and test your environment first, confirm your language, and leave yourself enough time to finish all the test cases. Get a correct version passing the tests before you refactor or optimise, and confirm with your recruiter whether your loop includes it.
PracHub interview research ↗How should I handle visa sponsorship?
Raise any sponsorship or work-authorisation needs with the recruiter during the initial screen, so they can be checked against current team needs before technical rounds are scheduled.
PracHub interview research ↗Which programming language should I use?
Use the language you know best. Python, Java and Go are the examples given for the language proficiency the role expects, and Python appears among the reported interview topics. Depth in one language helps more in live coding than light familiarity with several, because standard-library fluency saves time when you are coding under observation. Mention your experience with that stack when it is relevant.
PracHub Software Engineer practice ↗What kinds of coding questions are reported?
Reported algorithm questions include n-ary tree traversal, dynamic programming or graph problems, finding an optimal subset of an array under a condition, optimising a brute-force approach to O(n log n) or better, refactoring code for memory efficiency, shortest path with dynamic barriers, backtracking, and finding the first unique character in a string. Reported practical tasks include adding an API endpoint to a backend skeleton, extending a feature for a new edge case, debugging a failing test, and designing classes for a domain model such as a payment processor or notification system.
PracHub Software Engineer practice ↗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-24 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-24 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-24