Interview guides · Arch Global Services Philippines
Arch Global Services Philippines Software Engineer Interview Guide
Arch Global Services Philippines Software Engineer interview guide: reported topics, project walkthrough, coding, SQL and a 7-day prep plan.
The 60-second brief
Candidate reports do not describe the company's products or customers, so this guide does not guess at them. What they do describe is the job: a Software Engineer owns work from analysis and design through coding, testing and production support. The output is design specifications, source code and test scripts, produced in an Agile process alongside Digital Product Managers, Business Systems Analysts, Agile Program Managers and Software Architects. The role also covers tracking engineering risks, following enterprise architecture principles, resolving user issues in production and keeping documentation current.
This guide is for candidates applying for the Software Engineer role at Arch Global Services Philippines. It covers the reported questions (project architecture, HashMap versus TreeMap, API authentication, component lifecycle in React or Angular, SQL tuning), the fundamentals candidates say come up (OOP, memory management, threading, data structures) and how to turn your own project history into answers. It does not claim a round order, because none has been reported.
Candidates describe a process of roughly 2 to 4 weeks, running from initial screenings to final managerial discussions. No round-by-round order or format has been reported, so preparation is built around the reported topics rather than a fixed sequence.
Focus areas
- Java
- Data Structures & Algorithms
- Problem Solving
Overview
The reported topic mix is Java, data structures and algorithms, problem solving, coding tests and a project-based technical discussion. The role listing names Python as a must-have skill, with Power BI, VBA, incident management, cloud platforms and CI/CD as nice-to-haves. In practice that means two things need to be ready at once: a language you can defend at the level of OOP, memory management and threading, and a project you can explain from requirements down to the bug that hurt most.
Candidates report that practical experience is weighted over theory, and that interviews often start from what you have already built. The reported questions follow that pattern: describe the architecture and stack of your latest project, explain how you secure API access, explain a component lifecycle in the framework you prefer, and describe how you tune a slow SQL query. Each of these has a textbook answer and a version grounded in your own work. Prepare the grounded version, and keep the textbook version for when you are asked about something you did not personally build.
The day-to-day work described is stakeholder-facing: turning requests from product managers and analysts into specifications, code and tests, then supporting the result in production. That is why communication shows up in the reports alongside coding. Practise explaining a technical trade-off in plain words, and prepare one story about a production issue where you found the cause, fixed it and recorded what happened.
Many rounds are reported to include coding assessments or live problem-solving, and candidates are told to practise without leaning on IDE features. Do your coding practice in a plain editor, say your edge cases before you code, and finish each solution with its time and space cost and the trade-off you made.
Where to focus your preparation
No sequence of rounds has been reported, so the loop is covered as one preparation block built from the reported topics, with a reminder to confirm the real format with your recruiter.
Preparation focus
Candidates report stages that run from initial screenings to final managerial discussions, with coding assessments or live problem-solving in many rounds and a long discussion of past projects. No stage-by-stage order, format or length has been reported, so ask your recruiter which stages you will face and whether you will write code. Work the reported questions in three groups: a project walkthrough (architecture, stack, hardest bug, business impact), fundamentals (HashMap versus TreeMap, OOP, memory management, threading, SQL tuning, component lifecycle) and live coding with edge cases and trade-offs said aloud.
What to demonstrate
- Whether you can explain a past project end to end, including why each technology was chosen and what the result was
- Command of core concepts in your main language: OOP, memory management, threading and data structures
- Working code and clear reasoning on a coding task, including edge cases and trade-offs
- Clear communication with non-technical stakeholders, which candidates report as part of the evaluation
How to prepare
- Write a one-page architecture sketch of your latest project: components, data flow, stack, one decision you would change, and the hardest bug with its cause and fix
- Write a short note per concept in your main language: how OOP is applied in your code, how memory is managed and how threads are synchronised
- Solve coding questions in a plain editor and finish each with complexity and edge cases
- Ask your recruiter for the stage sequence, whether coding is live, and which language you may use
Where candidates lose points
Describing the project as a list of technologies with no reason for any of them
For each component in your architecture sketch, write the alternative you rejected and why. The reported question asks for the stack and the reasoning, so a rehearsed 'why' per choice beats a longer list.
Putting a technology on the resume that you cannot go deep on, whether Java, Python, React, SQL or a CI/CD tool
Reread the resume line by line before the loop. For each item, prepare one thing you built, one thing that went wrong and one detail that only the person who used it would know. Cut lines you cannot defend.
Answering HashMap versus TreeMap with only 'HashMap is faster'
State the structure behind each (hash table versus red-black tree), the cost of get and put (average O(1) versus O(log n)), ordering and range queries, null-key handling, and the need for hashCode/equals versus a Comparator.
Tuning a slow SQL query by guessing, adding indexes or rewriting joins before reading the execution plan
Start from the plan: scans versus index use, row estimates against actual rows, join order and sorts. Then change one thing, measure again, and be ready to say what an extra index costs on writes.
Staying quiet while coding, or taking a vague prompt as complete
Reported tips say prompts can arrive with limited detail. Ask about input size, duplicates, empty input and expected output before you write code, say the plan aloud, and practise in a plain editor without autocomplete.
Questions & practice
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?
Track a rolling failure rate per destination for circuit decisions
Easy · sliding window · ring buffer · circuit breaker
The egress service delivers about 1,500 webhooks per second across roughly 40,000 destinations, each call bounded by a 10 second timeout. Maintain, per destination, the failure rate over the trailing 60 seconds so a caller can ask before dispatch whether the circuit should open. Attempts arrive as (destination_id, finished_at_ms, outcome). Requirement: amortised O(1) per attempt, with total memory bounded by the destination count rather than by traffic. Give the structure, its exact memory, and the rule that stops a destination with three attempts from opening a circuit.
Approach
- Name the exact-deque version and then reject it as the default. Holding timestamps and advancing a tail pointer past anything older than now minus 60 seconds is a correct two-pointer window at amortised O(1) per attempt, but its memory tracks in-window traffic, so one destination in a retry storm holds hundreds of thousands of entries while thousands of quiet destinations hold none.
- Use a ring of 60 one-second buckets per destination, each bucket a pair of counters for attempts and failures. On an attempt, advance the ring by the elapsed whole seconds, zeroing at most min(elapsed, 60) buckets, then increment the head. That is amortised O(1) with a fixed footprint per destination.
- State the footprint: 60 buckets times two 4-byte counters is 480 bytes of payload per destination, so 40,000 destinations is roughly 20 to 25 MB with per-entry overhead, bounded by the catalogue rather than by the rate. The cost is granularity, since the oldest bucket ages out in whole seconds, which is far tighter than the decision needs.
- Require a minimum sample before the circuit may open. A destination with three attempts and three failures reads as 100 percent and is not evidence; a floor of roughly 20 attempts in the window makes the ratio meaningful, and below that floor use a run of consecutive failures as the trigger instead.
Follow-up
- The fleet is 30 instances and each sees roughly a thirtieth of a destination's traffic. Where does the rate actually live, and what does a per-instance answer get wrong?
- A destination answers in 9.5 seconds and succeeds. It is not failing but it is consuming your per-destination concurrency. What signal should open the circuit here?
Identify the heaviest tenants in a five-minute window under memory pressure
Medium · top-k · heavy hitters · streaming
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.
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?
How do you approach query optimization and performance tuning in SQL?
Medium · Technical and Domain Expertise
Approach
- Measure before changing anything: find the slow statements from the slow query log, pg_stat_statements (PostgreSQL) or Query Store (SQL Server), and capture the actual plan with EXPLAIN ANALYZE or the actual execution plan. Compare estimated against actual row counts; a large gap usually points to stale statistics, so refresh them (ANALYZE / UPDATE STATISTICS) first.
- Read the plan for the expensive node: full table scans on selective filters, nested-loop joins over large inputs, sorts or hashes spilling to disk, and key lookups that a covering index (INCLUDE columns) would remove.
- Index for the query shape: a composite index serves predicates on its leading columns, so order it with equality columns before the range or sort column. Keep predicates sargable: a function on the indexed column (WHERE YEAR(created_at) = 2024), a leading wildcard (LIKE '%abc') or an implicit type conversion blocks an index seek, so rewrite them as ranges or matching types.
- Rewrite the query itself: select only the columns needed, filter before joining and aggregating, use EXISTS for existence checks, replace deep LIMIT/OFFSET with keyset pagination, and fix N+1 query patterns coming from the application or ORM.
- Weigh the cost and verify: every index slows inserts and updates and takes storage, so check write paths too. Re-run against production-like data volumes, compare time and rows or pages read before and after, and keep the plan so a later regression can be spotted.
Follow-up
- The table has an index on the filtered column but the plan still shows a full scan. What are the possible reasons?
- With a composite index on (customer_id, created_at), which of these queries can seek on it: filter by customer_id only, by created_at only, or by both with ORDER BY created_at?
- A reporting query over a very large table stays slow after indexing. What would you try next?
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?
Explain the architecture of your most recent project and the specific tech stack you utilized.
Medium · Technical and Domain Expertise
Approach
- Open with the business problem and who used the system in one or two sentences, then trace one real request end to end: client or caller, API or service layer, business logic, data store, external integrations, and how the response or job result gets back.
- Justify each stack choice against an alternative you rejected and the constraint behind it, e.g. "I chose PostgreSQL over a document store because the reporting joins across orders and customers needed transactions and foreign keys." Stating a choice without its reason says nothing about your judgement.
- Separate your own work from the team's: say "I built the ingestion service and its retry logic" rather than "we built the platform", and be ready to go one level deeper into any component you name.
- Cover what keeps it running: how it is built and deployed (CI/CD pipeline, environments), how it is tested, how failures show up in logs or alerts, and how production issues were triaged. Give rough volumes (requests per day, rows, data size) only if you can defend them.
- Close with one concrete hard problem (a bottleneck, data inconsistency or bug), how you diagnosed and fixed it, and one thing you would design differently now, with the trade-off that change would bring.
Follow-up
- What was the most difficult bug you hit in that project, and how did you debug it?
- If traffic or data volume grew ten times, which component would fail first and what would you change?
- Why that framework and database rather than the obvious alternative, and what would make you switch?
What are the primary differences between common data structures, such as a HashMap and a TreeMap?
Medium · Technical and Domain Expertise
Approach
- Structure: HashMap is a hash table (array of buckets indexed by the key's hashCode); TreeMap is a red-black tree ordered by the keys' natural ordering or a supplied Comparator. HashMap makes no ordering guarantee; TreeMap iterates in sorted key order.
- Complexity: HashMap get/put/remove are O(1) on average, with resizing (capacity doubles once size exceeds capacity times the 0.75 default load factor) amortised in. Collisions degrade a bucket; since Java 8, large buckets are converted to balanced trees, which bounds lookups at O(log n) for Comparable keys. TreeMap get/put/remove are O(log n) guaranteed.
- Contracts: HashMap relies on equals and hashCode being consistent, so overriding one without the other or mutating a key after insertion loses entries. TreeMap decides key equality with compareTo/compare, so a comparator inconsistent with equals can merge distinct keys. HashMap allows one null key; TreeMap with natural ordering throws NullPointerException on a null key; both allow null values.
- When to pick which: HashMap for plain lookup and counting; TreeMap when you need ordered iteration or range and nearest-key queries through NavigableMap (floorKey, ceilingKey, headMap, tailMap, subMap, firstKey, lastKey); LinkedHashMap when you need insertion or access order (an LRU cache via removeEldestEntry).
- Concurrency and Python: neither HashMap nor TreeMap is thread-safe; use ConcurrentHashMap, or ConcurrentSkipListMap for a concurrent sorted map. In Python, dict is a hash map that preserves insertion order (3.7+), and there is no built-in sorted map: use the bisect module on a sorted list or the third-party sortedcontainers SortedDict.
Follow-up
- What happens if a class overrides equals but not hashCode and you use it as a HashMap key?
- How would you find all events between two timestamps, or the latest event before a given time, and which map supports that directly?
- How would you implement an LRU cache in Java, and what changes if several threads use it?
Can you explain the lifecycle of a component in your preferred framework (e.g., React or Angular)?
Medium · Technical and Domain Expertise
Approach
- Pick one framework and say so. For React function components, describe the cycle as render (a pure function of props, state and context) then commit (React applies DOM changes), then effects: useLayoutEffect runs synchronously after DOM mutation before the browser paints, useEffect runs after paint.
- Explain mount, update and unmount through useEffect: an empty dependency array runs once after mount; listed dependencies re-run it when any of them changes; no array re-runs it after every render. The cleanup function runs before the next run of that effect and on unmount, which is where subscriptions, timers and listeners are released.
- Map it to class components if asked: constructor, render, componentDidMount, shouldComponentUpdate, getSnapshotBeforeUpdate, componentDidUpdate, componentWillUnmount. Note that React 18 StrictMode in development mounts, unmounts and remounts components to surface missing cleanup, so an effect running twice locally is expected.
- For Angular, give the hook order: constructor, ngOnChanges (when input bindings change, first before ngOnInit), ngOnInit (once), ngDoCheck, ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked, then ngOnDestroy. Put data loading in ngOnInit rather than the constructor and release subscriptions in ngOnDestroy (or use the async pipe or takeUntilDestroyed).
- Finish with the bugs the lifecycle causes and how you avoid them: a fetch that resolves after its props changed (ignore flag or AbortController in cleanup), stale closures from missing dependencies, setState after unmount, and leaked RxJS subscriptions. Tie one of these to a real case from your project.
Follow-up
- A component fetches data whenever an id prop changes, and fast switching shows the wrong result. How do you fix it?
- Why might an effect run twice in development but once in production?
- In Angular, what changes about when a component's view is checked if you set ChangeDetectionStrategy.OnPush?
Listing latency scales with page size, not with filters
Easy · n+1 · latency · query batching
The tenant listing endpoint reads resource filtered by tenant_id and status, ordered by updated_at DESC, and returns each row plus the owner's display name from app_user and the actor of that resource's latest resource_revision. p99 is 55 ms at 10 rows per page and 1.4 s at 200. Database telemetry shows 401 statements per request, each under 1 ms, and nothing in the slow-query log. Diagnose the cause and give the fix, stating the statement count per request and the p99 you expect afterwards.
Approach
- Read the counters before forming a theory. 401 statements for 200 rows is one driver query plus two per row, and sub-millisecond execution with an empty slow-query log rules out a bad plan. The time is round trips, which is why it is invisible in every per-query metric and scales with rows returned rather than with filter selectivity.
- Name the two per-row statements from their normalised text: a single-row app_user lookup by user_id, and a resource_revision lookup by resource_id ordered by version DESC LIMIT 1. Confirm by dropping those two response fields and watching the statement count fall to one. That locates the calls in the serialisation layer, not the repository.
- Check that the arithmetic accounts for the whole gap. Measure one round trip to the replica in isolation; 400 trips at roughly 3 ms of network plus 0.2 ms of execution is about 1.3 s on top of a 55 ms baseline, which matches. If the multiplication had fallen short, the N+1 would only be part of the story and you would keep looking.
- Batch both lookups. Collect owner_user_ids and resource_ids from the driver query, then issue WHERE tenant_id = $1 AND user_id = ANY($2) for the users, and PostgreSQL's SELECT DISTINCT ON (resource_id) ... WHERE resource_id = ANY($2) ORDER BY resource_id, version DESC for the latest revision, which the UNIQUE (resource_id, version) index serves directly. On an engine without DISTINCT ON, use a lateral join or a row_number window. Three statements per request at any page size.
Follow-up
- The page size is capped at 200 today. What breaks first if it is raised to 2,000, and is it still this bug?
- How do you stop the next N+1 from reaching production, given that no individual query is slow and the endpoint's tests pass?
Suggested practice checklist
A seven-day plan built from the reported project walkthrough, the five reported questions and the fundamentals candidates mention.
Day 1 — Build your project walkthrough
- Pick the two projects on your resume you know best and write a one-page architecture sketch for each: components, data flow, stack and the reason for each stack choice
- Write down the most difficult bug from each project: symptoms, how you narrowed it down, the cause, the fix and what you changed afterwards
- Say the walkthrough aloud once and cut anything you cannot explain in a sentence
Deliverable: Two one-page architecture sketches, two bug write-ups and one spoken run-through of the project walkthrough.
Day 2 — Core language concepts and Map internals
- Write a short note on OOP as used in your own code, covering encapsulation, inheritance versus composition and polymorphism, each with a real example
- Write how memory is managed in your main language (stack and heap, garbage collection or reference counting) and list two ways a leak can happen
- Write a note on threading: a race condition you have seen, how locks or concurrent collections fix it, and what a deadlock needs
- Write the HashMap versus TreeMap comparison on one page: structure, cost, ordering, null keys and equals/hashCode versus Comparator
Deliverable: One-page notes on OOP, memory management and threading, plus a HashMap versus TreeMap comparison you can say from memory.
Day 3 — Live coding in a plain editor
- After each solution, state the time and space cost and one alternative you rejected
- Rewrite one solution in your second language if you list both Java and Python
Deliverable: Two working solutions with complexity and edge-case notes, written in a plain editor.
Day 4 — SQL query tuning
- Write how you would tune a slow query as a checklist: read the plan, check scans against index use, compare estimated and actual rows, review join order and sorts, then change one thing and re-measure
- Write the cost of each index you propose for the write path
Deliverable: A written SQL tuning checklist and a worked answer to the index practice question.
Day 5 — API security and component lifecycle
- Write your API authentication answer using the structure of a real application: mechanism, token lifetime, where tokens are stored, authorization checks and revocation
- List the abuse defences you would name: rate limiting, input validation, parameterised queries, CORS and CSRF
- Write the lifecycle of a component in React or Angular, whichever is on your resume: creation, updates, side effects and cleanup, with the hook or method for each
- Add one bug you have seen from a missed cleanup or a stale dependency
Deliverable: A written API security answer, a one-page component lifecycle note and one lifecycle bug story.
Day 6 — Debugging and stakeholder stories
- Write STAR outlines for a production issue, a risk you tracked and reduced, and a time you explained a technical decision to a non-technical stakeholder
Deliverable: A spoken debugging sequence and three written STAR outlines, each naming your own contribution and the result.
Day 7 — Full dry run
- Run a mock in order: project walkthrough, a fundamentals round, one coding problem in a plain editor, then a behavioral question
- Test your camera, microphone, connection and a quiet room, as candidates are advised for virtual interviews
- Write the questions you will ask your recruiter and interviewers about the stages, whether coding is live and how the team supports production
Deliverable: A completed mock with a list of weak spots, plus a final list of questions to ask.
Behavioural preparation
Candidates report that the later stages include managerial discussions and that answers are best structured with STAR, showing your own contribution and the business impact. The role works with Agile Program Managers, Digital Product Managers, Software Architects and Business Systems Analysts, so expect to explain technical decisions to people who do not write code, and to describe how you handled production problems and risk.
- What was the most difficult bug you encountered, and how did you debug it?
- Explain the architecture of your project and why you selected this particular tech stack.
- Tell me about a time you explained a technical concept or trade-off to a non-technical stakeholder and changed their decision.
- Describe a risk you spotted in your own work, how you tracked it and what you did to reduce it.
- Describe a production issue you resolved: how you found the cause, what you fixed and what you wrote down afterwards.
How do you handle API authentication and security in your applications?
Medium · Technical and Domain Expertise
Approach
- Anchor the answer in one real application. Say who calls the API (a browser app, another service, a partner) and name the mechanism you actually used: session cookie, API key, or OAuth 2.0 with short-lived access tokens. If you used JWTs, state what is validated on every request: signature, expiry, issuer and audience.
- Separate authentication from authorization. Describe how roles or scopes are checked on the server for each endpoint, and how you stop one user reading another user's records by changing an id in the URL. Deny by default and keep the check in a shared layer rather than copying it per route.
- Cover the credentials themselves: TLS on every hop, passwords hashed with bcrypt or Argon2, secrets in environment config or a secrets manager rather than in the repository, short token lifetime with refresh and a way to revoke. If tokens live in a browser, say where and what that choice exposes: httpOnly cookies need CSRF protection, local storage is readable by injected scripts.
- Add the surrounding defences you used: parameterised queries, input validation, rate limiting on login and token endpoints, a restrictive CORS policy, and logs that never contain tokens or passwords. Then close with a STAR beat from your own work (the situation, what you changed, the result). If you have not owned this yourself, say what you reviewed or would change instead of claiming it.
Follow-up
- Where do you store the access token in a browser application, and which attack does that choice leave open?
- How do you revoke a JWT before it expires, and what does that cost you in statelessness?
- How would you prove that one tenant or user cannot read another's data through the API?
Unblock an engineer without taking the keyboard
Easy · mentoring · leases · at-least-once
A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.
Approach
- Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
- Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
- Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
- Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.
Follow-up
- How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
- Their handler calls an external endpoint before recording that it did. What do you tell them to change first?
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?