Interview guides · CoffeeBeans
CoffeeBeans Software Engineer Interview Guide
CoffeeBeans Software Engineer interview guide: reported questions on Go slices, React, REST pagination, offline sync and AWS VPC, plus a 7-day plan.
The 60-second brief
Candidate reports do not name a specific CoffeeBeans product, customer or team, so this guide sticks to what the role itself is reported to involve. The Software Engineer position is described as covering backend work (services, API design, infrastructure other code depends on) and frontend work (responsive interfaces), with engineers connecting the two through APIs. The languages listed for the role are Java, Go, JavaScript with React, and Python. Cloud platforms (AWS or GCP) and containers (Docker, Kubernetes) are listed as helpful extras rather than requirements. Engineers are described as owning features from requirements through production, reviewing each other's code, working with product managers on technical requirements, and joining knowledge-sharing sessions, hackathons and design reviews. For preparation, this means questions can range from browser behaviour to cloud networking, so you need one language you can code in fluently plus working knowledge of the web stack around it.
This guide is for candidates interviewing for a Software Engineer role at CoffeeBeans, whether the team leans toward backend (Java or Go), frontend (JavaScript and React) or a mix. It covers the seven questions candidates report, the topic areas reported for the loop (JavaScript, React, data structures and algorithms, system design, API security and cloud infrastructure) and a seven-day plan. It does not cover pay or any particular team, because neither is reported in enough detail to prepare against.
Candidates report a mix of automated assessments and live sessions on design and architecture, plus a Management/CTO conversation. No fixed round order is reported, and timelines range from a single day to one to three weeks.
Focus areas
- JavaScript (JS)
- System Design
- React.js
Overview
The role is described as end-to-end ownership of features: writing production code, reviewing colleagues' code, agreeing technical requirements with product managers, and taking part in design reviews and hackathons. Work is said to span backend services and frontend interfaces. That breadth is why the reported question set jumps between a Go language-internals question, two browser and React questions, two API questions and two system design questions. Treat it as a full-stack screen where you are expected to have a primary language and a credible second layer.
The seven reported questions fall into three clusters. The first is language and frontend mechanics: how a Go array differs from a slice, how the Intersection Observer API works and what it is used for, and how class and functional components differ in React. The second is web API practice: what REST usability standards are for, and how to paginate in a web application. The third is architecture: online/offline synchronization in a mobile application, and how AWS load balancers and a VPC fit into a cloud design. Each cluster rewards a different kind of answer: precise definitions with a small example, a contract with trade-offs, or a diagram you can talk through.
Candidates also report evaluation areas beyond those seven: closures, promises, async/await and the event loop, DOM manipulation and event delegation, state management in larger applications, microservices communication, authentication and authorization for APIs, and scaling strategies. Data structures and algorithms appear in the reported topic mix and in the must-have skills, so keep at least one timed problem-solving session in every few days of preparation.
Reports stress explaining the reasoning behind a choice and asking clarifying questions before answering or coding. Rehearse that out loud: state what you assume, give the simple option first, then say why you would pick something sturdier and what it costs. Because the process varies from team to team, confirm early which stages you face and which language you will code in.
Where to focus your preparation
Candidates describe no fixed sequence of named rounds, so this guide treats the loop as one preparation block built from the stages and topics they mention.
Preparation focus
No ordered list of rounds is reported for CoffeeBeans. What candidates do mention: automated assessments for foundational knowledge, live sessions on design and architecture, online coding assessments where your compiler or IDE setup matters, and a Management/CTO conversation about past projects. Some candidates finish in a single day, while others report one to three weeks depending on the team. Approach this block in three passes. First, ask the recruiter which of these stages apply and which language you will use. Second, work the seven reported questions by cluster: Go and frontend mechanics, REST and pagination, then offline sync and AWS networking. Third, prepare a detailed account of every project on your resume, since the management conversation is reported to centre on past work, career goals and trade-offs.
What to demonstrate
- Foundational knowledge in your primary language (Java, Go, or JavaScript with React), which an automated or online coding assessment tests through working code
- Design and architectural reasoning, which candidates report being covered in live sessions
- Ability to discuss past projects, technical choices and trade-offs in the management conversation
- Whether you clarify the task before you implement or answer, a pitfall that candidate guidance highlights
How to prepare
- Ask the recruiter which stages apply, whether you will write code live or in an online assessment, and which languages are accepted
- Run a small program in your chosen language in the exact environment you will use, including reading input and printing output, before any assessment
- Answer the seven reported questions aloud, grouped as Go and frontend mechanics, REST and pagination, then offline sync and AWS networking
- Write a one-page summary of each resume project: the requirement, your design choices, the alternative you rejected and what you would change
Where candidates lose points
Answering the Go array versus slice question with 'slices are dynamic arrays' and nothing more
Say that an array's length is part of its type and that assigning or passing one copies every element, while a slice is a small header holding a pointer, a length and a capacity over an underlying array. Then show append: within capacity it writes into shared memory, past capacity it reallocates, so two slices can silently alias or silently stop aliasing. Run a ten-line example and be ready to predict its output.
Describing the Intersection Observer or class versus functional components at the level of a definition, with no mechanics
For the observer, name the callback receiving entries, the isIntersecting flag, root, rootMargin and threshold, one use such as lazy-loading images or infinite scroll, and when to call unobserve or disconnect. For React, map lifecycle methods to effects, explain this-binding in classes against closures in function components, and note that error boundaries still require a class component.
Answering the pagination or REST standards question with offset and limit only, or with a list of verbs and status codes
Compare offset pagination (simple, but pages shift when rows are inserted and deep offsets scan and discard rows) with cursor or keyset pagination (stable, index-friendly, but no jumping to page N). Tie both to a deterministic sort with a unique tiebreaker, and say what the response carries: next cursor, total only if affordable. For REST, explain what consistent resource naming, status codes, error bodies and versioning let a client do without reading source code.
Drawing boxes for the offline sync or AWS load balancer and VPC questions without fixing scope or stating failure behaviour
Ask what data is synced, how often users edit offline and whether two devices can edit the same record. Then commit to a local store, a queue of pending writes with idempotency keys and a conflict rule. For AWS, trace one request from the internet through a load balancer in public subnets to instances in private subnets, naming security groups and health checks, and say what happens when one availability zone is lost.
Coding silently, or starting before the task is understood, in an online assessment or live session
Restate the task, ask about input size and edge cases, state the simple approach and its cost, then the one you will write and its complexity, and narrate as you go. Beforehand, confirm your compiler or IDE configuration so a setup problem does not eat the session.
Questions & practice
Explain the difference between Array and Slice in Go.
Medium · Technical & Core Domain
Approach
- Start with the type: an array's length is part of its type, so [4]int and [5]int are different types. A slice is a small header (pointer to a backing array, len, cap) with a dynamic view over that array.
- Contrast the semantics. Assigning an array or passing it to a function copies every element, and arrays are comparable with == when the element type is. Passing a slice copies only the header, so callee and caller share the backing array. Slices compare only to nil.
- Explain append. When len < cap it writes into the shared backing array, so another slice over the same array sees the change (the classic aliasing bug). When cap is exhausted it allocates a larger array and copies, which gives amortised O(1) appends. Pre-size with make([]T, 0, n) when n is known.
- Cover the edge cases: a nil slice and an empty slice both have len 0 and accept append, but encoding/json marshals nil as null and empty as []. A small reslice of a huge array keeps the whole array alive, so copy() it out. s[lo:hi:max] caps capacity so a later append cannot overwrite neighbours.
- Say when an array fits: fixed-size values such as a [32]byte hash used as a map key (slices cannot be map keys). Use slices for almost everything else.
Follow-up
- A function appends to a slice parameter and the caller does not see the new element, yet an in-place index assignment is visible. Why?
- How do you remove the element at index i from a slice, and what happens to the backing array?
- Why can a [16]byte be a map key when a []byte cannot?
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?
Diff a projection against the primary without per-row point reads
Hard · reconciliation · range hashing · throttling
The listing projection has drifted and some rows show a stale version. The primary holds 40,000,000 resource rows across 12,000 tenants while serving 1,200 writes and 14,000 reads per second. The obvious repair, reading each resource row and comparing its version against the projection, is correct and would eventually finish. Explain precisely why it is unacceptable here, then give a diff that finds the differing rows, state its complexity, and make it safe to run against a live primary. Replication lag is usually under 100 ms and is not bounded.
Approach
- Quantify the naive cost rather than calling it slow: 40,000,000 point reads at even 0.5 ms each is over five hours serialised, and the only lever is concurrency, which is exactly what you cannot spend. The primary's pool is sized for the write path, and 40,000,000 random reads evict the buffer cache that sustains the 85 percent cache hit rate, so the audit degrades the system it is auditing.
- Replace random access with one ordered pass per side. Both sides can be read in (tenant_id, resource_id) order, which is a sequential scan on each and a merge join in O(n) time and O(1) memory. For a dense diff that is the whole answer, and it reads the primary once instead of 40,000,000 times.
- For the expected sparse case, compare range hashes instead of rows: partition the key space, compute per range an order-independent aggregate over hash(resource_id, version), compare aggregates, and descend only into ranges that differ. With d differing rows and branching factor B, at most d ranges mismatch per level, so the drill-down examines O(d log_B(n/d)) ranges and reads full rows only in mismatching leaves.
- Aggregate with a sum modulo 2^64 or a multiset hash, never XOR. XOR is order-independent but self-cancelling, so two rows wrong in the same way, or a row duplicated on one side, leave the range aggregate matching and the range is declared clean.
Follow-up
- The diff reports 900 stale rows. How do you decide between patching those rows and rebuilding the projection from resource_revision?
- Same job, but the projection lives in a search index that cannot be scanned in key order. What changes?
Explain why the owner filter ignores the listing index
Medium · composite index · query plans · sort elimination
The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.
Approach
- Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
- For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
- Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
- Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.
Follow-up
- 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
- A dashboard runs this for 40 owners in one page load. What changes about the design?
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?
How does the Intersection Observer API work, and what are its use cases?
Medium · Technical & Core Domain
Approach
- Describe the API: new IntersectionObserver(callback, { root, rootMargin, threshold }), then observer.observe(el). root null means the viewport. rootMargin grows or shrinks the root's box, and threshold is a ratio or an array such as [0, 0.5, 1] that sets which visibility crossings fire the callback.
- Explain the callback. The browser calls it asynchronously with IntersectionObserverEntry objects (target, isIntersecting, intersectionRatio, boundingClientRect, intersectionRect, rootBounds, time). It fires once when observation starts and again each time a threshold is crossed, so no scroll handler has to call getBoundingClientRect on every event.
- Name the use cases and how each one works: lazy-loading images (swap in src and unobserve; native loading="lazy" covers the simple case), infinite scroll through a sentinel element at the end of the list, impression tracking (for example 50% visible), pausing off-screen video or animation, and scroll-spy navigation.
- Cover cleanup in React: create the observer in useEffect and call disconnect() in the cleanup, keep the target in a ref, and guard against server rendering where the API does not exist.
- State the limits: it reports geometric intersection, not whether the element is occluded or actually painted. It does not report scroll direction (compare boundingClientRect.top across entries yourself), and callbacks are not frame-exact.
Follow-up
- Write a useInfiniteScroll hook that loads the next page when a sentinel appears, without firing duplicate requests.
- How would you count an ad impression only after it has been 50% visible for one continuous second?
- Why does this perform better than a scroll listener with getBoundingClientRect?
Discuss the differences between Class and Functional components in React JS.
Medium · Technical & Core Domain
Approach
- Class components extend React.Component, implement render(), keep state in this.state updated by setState (which shallow-merges) and use lifecycle methods: componentDidMount, componentDidUpdate, componentWillUnmount, shouldComponentUpdate or PureComponent. Handlers need this bound.
- Function components use hooks. useState replaces its value rather than merging it. useEffect with a dependency array and a cleanup function covers mount, update and unmount. useMemo, useCallback and React.memo cover memoisation, and custom hooks share logic where classes needed HOCs or render props.
- Give an accurate mapping. useEffect(fn, []) is close to componentDidMount but runs after paint, so DOM measurement before paint needs useLayoutEffect. In development, Strict Mode in React 18+ runs effects twice (mount, unmount, mount) to expose missing cleanup.
- Name the closure trap: each render captures its own props and state, so an interval or async callback can read stale values. Fix it with the functional updater setCount(c => c + 1), correct dependencies or a ref.
- Say where classes are still needed: error boundaries (getDerivedStateFromError and componentDidCatch) have no hook equivalent. New React features are hook-based, so new code is written as functions and existing classes are migrated when they are touched.
Follow-up
- How do you reproduce componentDidUpdate's prevProps comparison with hooks?
- Why does my effect run twice in development, and what does that tell you about the code?
- Migrate a class that derives state from props. What would you do in place of getDerivedStateFromProps?
What is the purpose of REST API usability standards?
Medium · Technical & Core Domain
Approach
- Open with the purpose. A usability standard makes every endpoint predictable, so a client who has learned one resource can guess the next. That lowers integration and support cost and lets tooling such as generated SDKs and docs work.
- Cover resource design and method semantics: plural nouns for collections (/orders/{id}), GET as safe and idempotent, PUT and DELETE as idempotent, POST as not idempotent, and PATCH for partial updates.
- Cover status codes and errors: 201 with a Location header, 204 for no body, 400 for malformed input, 401 for unauthenticated, 403 for forbidden, 404, 409 for conflicts, 429 with Retry-After. Use one error body shape everywhere, for example RFC 9457 problem details (type, title, status, detail).
- List the conventions to standardise: field casing, ISO 8601 timestamps, shared pagination, filter and sort parameters, a versioning rule (path /v1 or a header), idempotency keys so POST retries are safe, and bearer-token auth that never puts secrets in URLs.
- Explain enforcement: write the API in OpenAPI, lint the spec in CI against the standard, and announce deprecations with a documented window (for example a Sunset header) so callers you do not control can migrate.
Follow-up
- What is the difference between 401 and 403, and which do you return for another tenant's resource?
- How would you ship a breaking change to a field without breaking mobile clients you cannot force to update?
- How do you make a payment-creating POST safe to retry after a timeout?
How would you implement pagination in a web application?
Medium · Technical & Core Domain
Approach
- Offset pagination (LIMIT n OFFSET k) is simple and allows jumping to page N. The database still reads and discards k rows, so deep pages get slower, and inserts or deletes between requests make users see duplicates or skip rows.
- Keyset (cursor) pagination: ORDER BY created_at DESC, id DESC with WHERE (created_at, id) < (:last_created_at, :last_id) LIMIT n, backed by a composite index in the same order. Each page costs an index seek plus n rows, and the unique id tie-breaker keeps the order stable. The trade-off is no random page jumps.
- Response shape: return items plus an opaque cursor (for example base64 of the last sort key and the active filters) and has_more. Fetch n+1 rows to set has_more without a COUNT. An exact total on a large table is expensive, so make it optional or estimated.
- Client side: numbered pages keep page and size in the URL. Infinite scroll loads the next cursor when a sentinel becomes visible (Intersection Observer), dedupes by id, and cancels or ignores stale requests when filters change.
- Edge cases: cap the page size on the server, reject a cursor whose filters or sort no longer match, handle rows deleted between pages, and never sort only on a non-unique column.
Follow-up
- The product needs a total count on a table with tens of millions of rows. What do you do?
- How do you support a previous-page link with keyset pagination?
- Users can sort by price, which is not unique. How do you keep pages stable?
Explain how you would handle online/offline synchronization in a mobile application.
Medium · System Design & Architecture
Approach
- Make the local database (SQLite, Room or Core Data) the UI's source of truth. Each user write updates it and also appends an operation to an outbox table, with a client-generated UUID for the record and another for the operation.
- Sync engine: when connectivity returns, or from a scheduled background job (WorkManager, BGTaskScheduler), push the outbox in order with exponential backoff. The server dedupes on the operation id, so a retry after a lost response cannot apply twice.
- Pull changes with a delta cursor: the server keeps a change log with a monotonic version, and the client asks for changes since its last token. Deletes arrive as tombstones, and the client stores the new token only after applying the batch in one local transaction.
- Pick conflict resolution per data type and say what each costs: last-writer-wins on server-assigned versions (never device clocks), optimistic concurrency with a version or ETag that returns 409 or 412 for the client to merge, field-level merge, or CRDTs for collaborative data. Show the user any conflict that cannot be merged automatically.
- Edge cases: an operation that depends on an earlier one (create then update, with temporary ids remapped), a batch that fails partway through, an auth token that expires while offline, a local schema migration, and large attachments uploaded separately from metadata.
Follow-up
- The same record is edited on two devices while both are offline. Walk through what each device sees after syncing.
- How does the server work out what changed for a client that has been offline for two weeks?
- How would you test the sync logic against flaky networks and app kills?
How do AWS Load Balancers and VPC (Virtual Private Cloud) function within a cloud architecture?
Medium · System Design & Architecture
Approach
- A VPC is an isolated network in one region with its own CIDR block, split into subnets that each sit in one Availability Zone. A subnet is public when its route table sends 0.0.0.0/0 to an Internet Gateway. Private subnets reach the internet outbound through a NAT gateway placed in a public subnet.
- Traffic control: security groups are stateful allow-lists attached to network interfaces and can reference other security groups. Network ACLs are stateless, apply at the subnet level and evaluate numbered allow and deny rules in order, so return traffic needs its own rule.
- Load balancer types: an ALB works at layer 7 (HTTP/HTTPS, host and path routing, TLS termination, WebSockets, targets that are instances, IPs or Lambda). An NLB works at layer 4 (TCP/UDP/TLS, static IP per AZ, very high throughput, client source IP preserved). A Gateway Load Balancer fronts inspection appliances.
- Show the standard layout: an internet-facing ALB in public subnets across at least two AZs, app instances from an Auto Scaling group in private subnets registered to a target group, and a target security group that allows traffic only from the ALB's security group. Health checks take failed targets out of rotation, and the database sits in its own private subnets.
- Name the trade-offs: NAT gateway cost against VPC endpoints for S3 and DynamoDB, cross-zone load balancing (on by default for ALB, off by default for NLB), sticky sessions against stateless instances, and peering against Transit Gateway when there are several VPCs.
Follow-up
- An instance in a private subnet cannot download packages. How do you debug it, step by step?
- When would you put an NLB in front of a service instead of an ALB?
- How would you run a blue/green deployment behind the ALB?
Read latency spikes on a sixty-second sawtooth
Medium · cache stampede · single-flight · ttl
The cached listing read path serves about 14k reads/second at an 85% hit rate. p99 sits at 35 ms for 57 seconds, jumps to 900 ms for 3, and repeats. During each spike the primary shows several hundred identical listing queries starting within the same millisecond, all carrying one large tenant's id. Cache entries use a 60-second TTL. Give the mechanism, the ordered checks, the fix, and the correctness hazard your fix must not introduce.
Approach
- Match the period to a configured number before theorising about load. A spike every 60 seconds against a 60-second TTL is an entry expiring, and you confirm it by correlating spike timestamps with the entry's write time rather than with the traffic curve. If the period had matched a cron or a GC interval instead, this is a different investigation.
- Establish the concurrency of the miss. Several hundred identical queries in one millisecond means the miss path has no coalescing: every request that arrives between expiry and repopulation recomputes. The herd size is that key's arrival rate times its recompute time, so at 1.2k reads/second for the hot key and a 250 ms recompute you expect about 300 concurrent misses, which matches what is observed.
- Add single-flight on the miss path so one caller per key recomputes under a short-lived lock while the rest wait for its result. Prefer stale-while-revalidate where the read tolerates it: return the expired value immediately and refresh asynchronously, which removes the latency spike rather than serialising it into a queue of waiters.
- De-synchronise the keys. Write TTLs with jitter, for example 60 seconds plus or minus 10%, so a deploy or a mass invalidation does not align every key on the same second and turn a per-key herd into a fleet-wide one.
Follow-up
- The same sawtooth appears on a key that is invalidated on write rather than expired. Is that the same bug?
- How does your answer change if the recompute takes 4 seconds instead of 250 ms?
Suggested practice checklist
The plan follows the clusters of reported questions: setup and format on day one, language and frontend mechanics, APIs and pagination, then architecture, algorithms under narration and a full mock.
Day 1 — Confirm the format and set up your environment
- Ask the recruiter which stages apply (automated assessment, live session, Management/CTO conversation), whether you will write code, and which languages are allowed
- Run a small program with input and output in your chosen language, in the exact compiler or IDE you will use
- List every project on your resume with one line each on your role and the hardest technical choice
Deliverable: A recruiter reply on format and language, a program that compiles and runs in your assessment environment, and a project list with one hard choice per project.
Day 2 — Go slices and core data structures
- Write a Go program that shows array copy semantics, slice header sharing, and an append that crosses capacity, and annotate the output
- Explain out loud the difference between an array and a slice, including when you would choose each
- Solve one hash-map problem in your primary language, stating time and space complexity before you code
Deliverable: An annotated Go program, a recorded spoken answer to the array versus slice question, and one solved hash-map problem with complexity written beside it.
Day 3 — Browser, JavaScript and React mechanics
- Build an infinite-scroll or lazy-image list with an Intersection Observer and clean it up with unobserve
- Write a snippet mixing setTimeout, a resolved promise and an await, and predict the log order before running it
- Rewrite a class component with state and lifecycle methods as a function component with hooks, and list what maps to what
Deliverable: A working observer-based list, a predicted-then-verified event loop output, and a class-to-function conversion with a mapping table.
Day 4 — REST conventions and pagination
- Write the contract for a list endpoint: resource naming, status codes, error body, versioning and authentication
- Implement both offset and cursor pagination for it, and write down what each does when rows are inserted mid-browse
Deliverable: An endpoint contract, two pagination implementations with a comparison table, and a written explanation of the index problem.
Day 5 — Offline sync and AWS networking
- Sketch an offline-first mobile design: local store, pending-write queue, idempotency keys, conflict rule and retry behaviour
- Draw a VPC with public and private subnets, a load balancer, security groups and health checks, and trace one request through it
- Add API security to the sketch: where authentication happens, how authorization is checked, and how services call each other
Deliverable: One offline-sync diagram, one VPC diagram with a request trace, and a half-page note on authentication and authorization placement.
Day 6 — Algorithmic problem solving while narrating
- Solve two timed problems in your primary language, saying the brute-force approach, the target complexity and the edge cases aloud
- Review each solution for naming, structure and what you would say in a code review
Deliverable: Two solved problems and one practice solution, each with a complexity line and an edge-case list, plus review notes.
Day 7 — Full mock and project conversation
- Run a mock that mixes a coding problem, one frontend mechanics question and one design question, all spoken aloud
- Rehearse a five-point account of your two strongest projects: requirement, design, trade-off, result, change
- Write the questions you would ask about the team, its review practice and how work is split between frontend and backend
Deliverable: A completed mixed mock with a list of weak answers, rehearsed notes for two projects and a written list of questions to ask.
Project walkthroughs and the management conversation
Candidates report a Management/CTO conversation where preparation centres on past projects, career goals, how your work affects the business and the trade-offs behind your technical choices. Reports also advise being ready to discuss every project on your resume in detail. The prompts below are rehearsal material for that conversation, not quoted questions.
- Walk me through a project on your resume end to end: the requirement, the design you chose, the alternative you rejected and what you would change now.
- Tell me about a time a request was ambiguous. What did you ask before you wrote code, and how did the answers change your design?
- Describe a code review where you changed your mind, or where you changed someone else's. What was the technical argument?
- Describe how a feature you built affected a business outcome, and how you knew.
- Where do you want your career to go, and what in this role moves you toward it?
Argue against a design, lose, and commit anyway
Medium · disagreement · service boundaries · decision records
Describe a design you argued against and lost. State the failure you predicted as a named mechanism, not a feeling about complexity: two services that would need one transaction, a projection with no rebuild path, a write path with no idempotency key. Say what evidence you brought, what the decision maker weighed instead, and what you did after the decision was made: what you instrumented, what you wrote down, and whether the prediction came true. Five minutes.
Approach
- State the prediction in falsifiable form up front: the mechanism, the condition that triggers it, and the observable outcome. A prediction that cannot be checked also cannot be credited to you later.
- Show the evidence you had at the time and label each piece honestly as measured, analogous, or intuition. Keeping the intuition is fine; disguising it as data is the thing that erodes your standing in the next argument.
- Represent the opposing case at full strength, including the constraint you did not control: a fixed date, a team boundary, or the fact that the decision was cheap to reverse and yours was not.
- Make disagree-and-commit concrete. Name the artefact you left behind so the prediction could be settled without you: the alert and its threshold, the counter on the dashboard, the decision note that recorded the trade-off and the condition that would revisit it.
Follow-up
- What threshold on that alert would have proved you right, and did anyone ever look at it?
- If the same proposal arrived tomorrow with the same deadline, would you argue it the same way?
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?
Reverse your own decision and price the reversal
Medium · reversibility · measurement · migrations
Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.
Approach
- State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
- Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
- Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
- Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.
Follow-up
- What in that decision was irreversible, and did you know it was irreversible when you made it?
- How did you tell the people who had already built on top of the original decision?