cswork / libraryOpen practice workspace

Interview guides · Asmadiya Technologies

Asmadiya Technologies Software Engineer Interview Guide

Asmadiya Technologies Software Engineer interview guide: 3 reported rounds, Express and async questions, array and complexity practice, and a 7-day plan.

The 60-second brief

Candidates describe Asmadiya Technologies as a company that builds software for client solutions, with web and mobile applications at the center of the Software Engineer role. The reported role description covers backend work in the Node.js ecosystem with Express and RESTful APIs, plus interface work in a framework such as React Native. Day to day, candidates describe writing, testing and deploying code, joining code reviews, and turning product requirements into technical specifications alongside product managers and other engineers.

This guide is for candidates interviewing for a Software Engineer position at Asmadiya Technologies, especially if your strongest tools are JavaScript or TypeScript and Express. It covers the three reported rounds (Technical Screening, Practical Application, Team Alignment), the ten reported questions across technical coding, data structures and algorithms, and project experience, a seven-day plan, and behavioral prompts for the collaboration-focused final stage.

Candidates report three rounds spread over roughly 3-5 weeks: Technical Screening, Practical Application and Team Alignment, moving from foundational coding to applied problem solving and then a conversation about working with teammates.

Focus areas

  • Backend Development
  • API Development
  • Data Structures & Algorithms (DSA)

Overview

The reported role centers on web and mobile applications. Candidates list JavaScript or TypeScript, a backend framework such as Express, and data structures and algorithms as must-have skills, with React Native, cloud deployment pipelines and database design patterns as nice-to-have. For preparation, that means you should be able to build a small Express server from a blank file, connect it to storage, and explain how it behaves when requests fail or overlap.

The ten reported questions fall into three groups. Technical proficiency and coding: create an Express backend with a POST and a GET API, explain the logic behind your approach to a coding task, describe how you handle asynchronous operations in backend code, and describe your process for debugging a failed API request. Data structures and algorithms: an array manipulation problem, how you would optimize the time complexity of your current solution, and the trade-offs between data structures for storing user metadata. Project experience: a challenging project and your specific role, how you manage dependencies and version control in a team, and the most difficult technical hurdle in a previous project. Two of these (explaining your approach and optimizing your current solution) only make sense attached to a problem you have just solved, so practise narrating every solution, not only writing it.

Candidates also pass along a few habits worth rehearsing: talk through your reasoning while you work, get a simple version running before optimizing, avoid over-building, and be ready to explain the why behind each decision in your own resume projects. The plan below turns those habits into drills: build the server, break it on purpose and debug it, solve arrays with a brute-force-then-optimize routine, compare data structures for a concrete access pattern, and write your project stories in first person.

The Asmadiya Technologies interview process

Candidates report three rounds over roughly 3-5 weeks. The reported question groups are listed under the guide, but no report ties a specific question to a specific round, so prepare every group.

Technical Screening

Candidates describe Technical Screening as the first stage, an initial look at foundational technical knowledge through coding tasks. Because it is coding-based, the coding questions in this guide are the right practice material: the Express POST and GET server, the array manipulation problem and the data-structure trade-off question. Aim for working code that you can explain line by line, and keep the first version simple before you tune it.

What to demonstrate

  • Foundational technical knowledge shown through working code
  • Comfort with JavaScript or TypeScript, Express and core data structures
  • Ability to explain the reasoning behind the code you write

How to prepare

  • Write from memory an Express server with express.json(), a POST route that validates the body and returns 201, a GET list route, and a GET by id route that returns 404 for an unknown id.
  • Solve three array problems (hash map lookup, sliding window, prefix sums) and for each say the brute-force complexity first, then the improved one.
  • Prepare a short spoken explanation of why you chose a Map, array or set in each solution.

Practical Application

Practical Application is reported as an assessment of practical coding skills and problem-solving abilities. The reported backend evaluation areas are API design, request handling (data flow, error handling, authentication) and database integration, so expect to be asked to apply those rather than recite them. Practise tasks where something has to run end to end: an endpoint backed by storage, an async flow with several calls, and a request that fails and must be diagnosed.

What to demonstrate

  • Practical coding skill on applied backend tasks
  • Problem solving, including how you break a large task into smaller functions
  • Request handling: error handling, authentication and database integration as candidates describe them

How to prepare

  • Extend your Express server with input validation, an error-handling middleware, and a database call written with async/await.
  • Break your own API five ways (wrong content type, missing body parser, wrong method, thrown error inside an async handler, slow dependency) and write the symptom, first check and fix for each.
  • Practise the async answer: sequential awaits versus Promise.all versus bounded concurrency, and what each does to latency and failures.

Team Alignment

Team Alignment is described as the final stage: discussions about cultural fit and collaboration with potential teammates. The role description mentions code reviews and regular work with product managers and other engineers, so build your preparation around how you work with people. Have specific stories ready where your own role is clear, and be ready to describe how you handle version control and dependencies on a shared codebase.

What to demonstrate

  • Collaboration with potential teammates
  • Clarity when describing your own role and decisions in past work
  • Communication about technical choices

How to prepare

  • Write two project stories in first person, each with your role, the hardest technical hurdle, the cause, your fix and the result.
  • Write down your team workflow: branching, pull request review, lockfiles, and how you upgrade a dependency safely.
  • Prepare questions for the team about code review practice and how engineers and product managers hand off requirements.

Where candidates lose points

Building an Express server that only works on the happy path: no express.json(), no body validation, 200 for everything, no 404 on an unknown id

Keep a checklist for every route you write: parse the body, validate it, return 201 on create and 400 on bad input, 404 on a missing resource, and send errors through one error middleware. Start with an in-memory store and add layers only after both routes work.

Writing the array solution silently, then having no answer when asked to explain it or optimize it

For every problem, say the input constraints, the brute-force approach with its complexity, the repeated work you will remove, and the final time and space cost. Practise this out loud on arrays so the explanation and the optimization come from the same script.

Describing async handling as just 'use async/await', with no mention of error propagation, concurrency or blocking

Prepare the three-part answer: how errors reach the error handler, when to run calls in parallel versus in order (and a cap for large fan-out), and what to do with CPU-bound or slow work. Be ready to name the forEach-with-await bug.

Answering the failed API request question with 'I would check the logs' and no sequence

Use a fixed order: reproduce with curl, read the status code and response body, verify method, URL, headers, content type and auth against what the server expects, find the matching server log entry, check downstream dependencies, then fix and add a regression test.

Telling project stories in the group voice, with no clear role, cause or result for the hardest hurdle

Write each story in first person: what you owned, the specific technical problem and its cause, the options you weighed, what you did and the outcome. Review the reasoning behind each choice in your resume projects, including trade-offs you would now make differently.

Questions & practice

Explain the logic behind your chosen approach for a specific coding task.

Medium · Technical Proficiency and Coding

Approach

  1. Restate the task with its constraints first: input type and size, whether values can be negative or duplicated, whether the input may be mutated, and what to return when there is no answer.
  2. Name the brute-force version and its cost (for example, checking every pair is O(n^2)), then say which observation lets you skip work: a hash lookup, sorted order, a running prefix, or a reusable subresult.
  3. State the invariant that makes the chosen approach correct, such as 'everything left of the write pointer is already deduplicated' or 'the window always satisfies the constraint', and show it holds after each step.
  4. Give time and space complexity for the final version and the trade-off you accepted, for example O(n) time bought with O(n) extra memory for a hash map.
  5. Trace one small example by hand, including an edge case (empty input, a single element, all duplicates), before calling the code finished.

Follow-up

  • Why did you not use the alternative approach (sorting first, or a different data structure), and when would it win?
  • What input would break this solution or push it to its worst case?
  • How would you test it: which cases go into the test list and why?

Solve a given problem related to array manipulation.

Medium · Data Structures and Algorithms

Approach

  1. Clarify before coding: sorted or not, negatives allowed, duplicates allowed, may the array be modified in place, and what to return for an empty array or when no answer exists.
  2. Match the problem to a pattern: a hash map for pair lookups (two-sum in O(n) time, O(n) space); two pointers on sorted input; a sliding window for contiguous subarrays under a monotone constraint such as positive numbers; prefix sums with a hash map of counts for 'subarray sums to k', which also works with negatives; Kadane's algorithm for maximum subarray sum.
  3. For in-place problems (remove duplicates from a sorted array, move zeros) keep a read pointer and a write pointer: O(n) time and O(1) extra space, and say whether the original order is preserved.
  4. Check JavaScript details: arr.sort() with no comparator compares values as strings, so numbers need (a, b) => a - b; integers above Number.MAX_SAFE_INTEGER (2^53 - 1) lose precision, so large sums may need BigInt.
  5. Test empty, single-element, all-equal, all-negative and already-sorted inputs, then state the final time and space complexity.

Follow-up

  • Can you do it with O(1) extra space, and what does that cost in time?
  • What changes if the array arrives as a stream and cannot be held in memory?
  • If the input were guaranteed sorted, which part of your solution becomes unnecessary?

How would you optimize the time complexity of your current solution?

Medium · Data Structures and Algorithms

Approach

  1. Find the dominant cost first: usually a nested loop, a linear search inside a loop (includes or indexOf inside a for makes the whole thing O(n^2)), or recomputation of the same subresult.
  2. Apply the standard replacement for that bottleneck: a Set or Map for repeated membership checks (O(1) average); sort once in O(n log n) and then use two pointers or binary search; prefix sums for repeated range sums; a monotonic deque for sliding-window max or min in O(n); memoization or bottom-up DP for overlapping recursive calls.
  3. State the trade-off explicitly: most of these spend O(n) extra memory to save time, and a hash structure is O(1) on average but can degrade with heavy collisions.
  4. Name the lower bound so you know when to stop: if every element must be read, O(n) is the floor, and comparison-based sorting cannot beat O(n log n).
  5. Confirm the gain on realistic input sizes with a quick benchmark, and keep the simpler version when n is small enough that the difference does not matter.

Follow-up

  • What does the faster version cost in memory, and is that acceptable here?
  • Can this be done faster than O(n log n)? If not, why not?
  • How would you prove the optimized version returns the same results as the original?

Explain the trade-offs between different data structures for storing user metadata.

Medium · Data Structures and Algorithms

Approach

  1. Start from the access pattern: lookup by id, lookup by email, range queries (users who signed up last week), prefix search on names, or full iteration for export. Each favours a different structure.
  2. Hash map keyed by user id: O(1) average get, set and delete, no ordering. In JavaScript a Map keeps insertion order, accepts any key type, exposes size, and avoids prototype-key collisions that a plain object has; a plain object serializes directly to JSON.
  3. Array of records: cheap iteration and serialization, but O(n) lookup. A sorted array gives O(log n) lookup by binary search with O(n) inserts; a balanced tree (or a B-tree index in a database) gives O(log n) lookup and insert plus ordered range scans.
  4. Secondary lookups need secondary indexes, for example a second Map from email to id, and every write must update both; a trie supports prefix search on usernames; a Set holds per-user tags for O(1) average membership tests.
  5. At the storage level, fixed relational columns give validation and simple indexing, while a JSON document column suits sparse fields that vary per user but is harder to constrain and query. Say which fields go where and why.

Follow-up

  • You need lookup by both id and email. How do you keep the two indexes consistent on update and delete?
  • Metadata fields differ from user to user. Do you add columns or store a JSON document, and what does each cost later?
  • The data no longer fits in one process's memory. What changes?

Keep soft-deleted accounts from blocking re-registration

Easy · soft delete · unique constraints · engine differences

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.

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?

Stop tag and share joins from fanning out a page

Medium · join fan-out · semi-joins · many-to-many

resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.

Approach

  1. Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
  2. Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
  3. Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
  4. Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.

Follow-up

  • The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
  • A resource can be shared with the same user twice under different permissions. Does your count change, and should it?

Create a backend server using Express and implement a POST and GET API.

Medium · Technical Proficiency and Coding

Approach

  1. Separate the app from the listener: build const app = express() in one module, export it, and call app.listen() in a separate entry file so a test runner such as supertest can import the app without opening a port.
  2. Register app.use(express.json()) before the routes. Without it req.body is not populated from a JSON payload, and a client that omits Content-Type: application/json gets the same empty-body symptom.
  3. POST /items: validate the required fields and types, return 400 with a structured error body on failure, otherwise assign an id, store the record (a Map keyed by id is enough to start) and return 201 with the created object and a Location: /items/:id header.
  4. GET /items/:id returns 200 with the record or 404 when the id is unknown; GET /items returns a list with a limit query parameter and a cursor or offset so the response size stays bounded.
  5. Finish with one error middleware with four arguments (err, req, res, next). In Express 4 a rejected promise in an async handler is not forwarded automatically, so wrap handlers in try/catch and call next(err). Express 5 forwards rejected promises to the error middleware.

Follow-up

  • A client times out on POST and retries. How do you stop the retry from creating a duplicate record (for example an Idempotency-Key header or a unique constraint)?
  • How would you swap the in-memory Map for a database, and which status code do you return when an insert violates a unique constraint?
  • Where would authentication and input validation live: in each handler or as middleware, and why?

Describe a challenging project you worked on and your specific role in it.

Medium · Project Experience

Approach

  1. Pick a project where you owned a clear piece (an API, a screen, a data pipeline), and describe the system and its users in two sentences before going into detail.
  2. Separate your work from the team's: use 'I' for what you designed, wrote, or decided, and name who owned the adjacent parts.
  3. Name the specific difficulty (a performance limit, an unclear requirement, an integration with an unreliable API, a deadline) and the options you weighed, with the reason you chose one.
  4. Close with a result you can check (latency, crash rate, delivery date, users served) and one thing you would do differently now.
  5. Know every technical choice in it well enough to defend: why Express or React Native, if you used them, why that database, why that library. Stories that stay at the 'we built it' level fall apart under the first detailed question.

Follow-up

  • Which alternative did you reject, and what would have happened if you had chosen it?
  • What part of the system would break first at ten times the load?
  • Did anyone disagree with your approach, and how was it settled?

How do you manage dependencies and version control in a team environment?

Medium · Project Experience

Approach

  1. Explain semantic versioning in package.json: ^1.2.3 accepts any version below 2.0.0, ~1.2.3 accepts versions below 1.3.0, and an exact version pins it. Keep dependencies and devDependencies separate.
  2. Commit the lockfile (package-lock.json or the yarn/pnpm equivalent) and install with npm ci in CI, which installs exactly what the lockfile records and fails if it disagrees with package.json.
  3. Update dependencies on purpose: one upgrade per pull request, read the changelog for majors, run the tests, and use npm audit or an automated update tool to surface vulnerable versions.
  4. For Git, describe short-lived feature branches off main, pull requests with review and passing CI before merge, and a protected main branch. Rebase your own branch to stay current; never rewrite history on a branch others have pulled.
  5. Handle conflicts by syncing with main often. For a lockfile conflict, resolve package.json first, then regenerate the lockfile with an install instead of hand-editing it.

Follow-up

  • A transitive dependency has a security advisory and the direct dependency has not released a fix. What do you do (for example npm overrides)?
  • A bad merge reached main. How do you undo it without rewriting shared history (for example git revert -m 1 on the merge commit)?
  • Merge commits or squash-and-rebase: which do you prefer for this team and why?

What was the most difficult technical hurdle you faced in your previous project, and how did you overcome it?

Medium · Project Experience

Approach

  1. Choose a hurdle with a real technical root cause, such as a race condition, a memory leak, a slow query, a flaky integration or a mobile build failure, and avoid one that was only a scheduling or people problem.
  2. Describe the symptom precisely, then the narrowing: the hypotheses you formed, what you measured to rule each out (logs, a profiler or heap snapshots, EXPLAIN on a query, a minimal reproduction), and which result pointed at the cause.
  3. Explain the fix and why you picked it over the alternatives, including what it cost (added complexity, a schema change, a dependency).
  4. Show that it worked with a number or an observable change, and say what you added so it would not recur: a test, an alert, a lint rule, a note in the docs.
  5. Keep the story in the first person. A fix you cannot explain ('it started working after I changed X') does not count as overcoming the hurdle.

Follow-up

  • How did you confirm that was the cause and not a coincidence?
  • What would you have done if the fix you chose had not been allowed?
  • Who else did you involve, and at what point?

Describe your process for debugging a failed API request.

Medium · Technical Proficiency and Coding

Approach

  1. Capture the exact failing request first: method, URL, headers, body and the response status and body. Reproduce it outside the client with curl or Postman to learn whether the problem is in the caller or the server.
  2. Classify by outcome. No response at all points to DNS, a refused connection, a timeout or CORS (in a browser the request can reach the server while the browser blocks the response, and a failed OPTIONS preflight shows up in the network tab). A 4xx points at the request; a 5xx points at the server.
  3. For 4xx, check the likely causes in order: 400 for a payload that fails validation or arrives unparsed because Content-Type is missing, 401/403 for a missing or expired token, 404 for a wrong path, wrong HTTP method or a route registered after a catch-all, 413 for an oversized body.
  4. For 5xx, find the server log line for that request, ideally by a request id passed back in a response header, read the stack trace, and check downstream calls (database, third-party API) and anything that changed recently: a deploy, an environment variable, a dependency version.
  5. After the fix, add a test that reproduces the original request, and add the logging that would have made the diagnosis faster (request id, status, latency, error message without secrets).

Follow-up

  • The request works in Postman but fails from the browser. What do you check?
  • The failure is intermittent (occasional 502 or 504). How does your process change?
  • How would you trace a request that passes through several services before it fails?

Suggested practice checklist

Seven days built around the three reported rounds and the Express, JavaScript or TypeScript and algorithms stack: build, run async code, debug, solve arrays, compare data structures, write project stories, then rehearse everything together.

Day 1 — Rebuild an Express server from memory

  • Write an Express server with express.json(), POST /items (validate, return 201 with the created item), GET /items and GET /items/:id returning 404 when missing, backed by an in-memory Map.
  • Add an error-handling middleware and a not-found handler, then write a curl script that exercises the success, 400 and 404 paths for each route.
  • Write down the status code you return for each outcome and why.

Deliverable: A running Express server file with POST and GET routes, plus a curl script covering the success, validation-failure and not-found paths.

Day 2 — Async operations and storage

  • Replace the in-memory store with a database you know, using async/await, and keep errors flowing to your error middleware.
  • Write three versions of a fan-out over ten simulated 200 ms calls: sequential, Promise.all, and bounded concurrency; record each timing. Then reproduce the await-inside-forEach bug and fix it.

Day 3 — Debug a failed API request

  • Break your day-1 server in five ways: wrong content type, missing body parser, wrong HTTP method, an exception thrown inside an async handler, and a dependency that times out.
  • For each failure, write the symptom, your first check, the cause and the fix.
  • Record a spoken answer that walks through your debugging order from reproduction to regression test.

Deliverable: A five-row table of failure, symptom, first check, cause and fix, plus a recorded spoken answer to the debugging question.

Day 4 — Array problems with an optimization step

  • Solve four array problems that use different patterns: hash-map lookup, sliding window, prefix sums, and two pointers on a sorted array.
  • For each, write the brute-force version and its complexity first, then the optimized version and its time and space cost.
  • Re-explain one solution out loud as if to a teammate, including why the naive version is too slow.

Deliverable: Four solutions, each with brute-force and optimized complexities written next to the code, and one recorded explanation.

Day 5 — Data structures for user metadata

  • List the access patterns for a user-metadata store: lookup by id, lookup by email, range by signup date, full iteration, frequent updates.
  • Compare an array, a Map or object, a Set, and a sorted array with binary search on lookup, insert, ordered iteration and memory, and choose one design per access pattern.
  • Practise a short spoken answer that states the access pattern first, then the structure and its trade-off.

Deliverable: A comparison table of structures against access patterns with your chosen design, plus a short spoken-answer script.

Day 6 — Project stories and team workflow

  • Pick two past projects and write each story in first person: your role, the hardest technical hurdle, its cause, your fix and the result.
  • Write your version-control and dependency workflow: branching, pull request review, lockfiles, and how you upgrade a dependency safely.

Deliverable: Two first-person project stories, a half-page team workflow description, and rehearsed answers to the two practice prompts.

Day 7 — Full rehearsal

  • Run a timed pass in loop order: build the Express endpoints, solve an array problem and answer an optimization follow-up, then give spoken answers on async handling and debugging a failed request.
  • Tell one project story aloud, then record and review the whole session and list the three weakest points.
  • Write three questions to ask potential teammates about code review and how requirements reach engineers.

Deliverable: A recorded rehearsal, a list of three weak spots with a fix for each, and three questions for the team.

Project and teamwork stories

Candidates describe the final round, Team Alignment, as discussions about cultural fit and collaboration with potential teammates, and the project-experience questions ask for your specific role, your hardest technical hurdle and your team workflow. Prepare stories where your own decisions are visible and where a teammate, a product manager or a reviewer is part of the plot.

  1. Describe a challenging project you worked on and your specific role in it.
  2. What was the most difficult technical hurdle you faced in your previous project, and how did you overcome it?
  3. How do you manage dependencies and version control in a team environment?
  4. Tell me about a time you turned a vague product requirement into a technical plan with a product manager or another engineer.
  5. Describe a code review where you disagreed with the feedback, and how it was resolved.
  6. Tell me about a time you shipped a simple working version first and improved it afterwards.

How do you handle asynchronous operations in your backend code?

Medium · Technical Proficiency and Coding

Approach

  1. Start from the runtime model. Node runs your JavaScript on one thread with an event loop, so database, HTTP and file calls are awaited without blocking other requests, while CPU-heavy work (large JSON transforms, hashing, image processing) does block them and belongs in a worker thread or a background job.
  2. Show the Express pattern: async (req, res, next) => { try { const user = await repo.find(req.params.id); res.json(user); } catch (err) { next(err); } }. Express 4 does not forward a rejected promise from an async handler to the error middleware (Express 5 does), so forward errors with next or use a small wrapper, and register one central error handler.
  3. Choose the concurrency shape per case. Independent calls run together with Promise.all (rejects on the first failure) or Promise.allSettled when partial results are acceptable; dependent calls stay sequential. Avoid await inside forEach, which does not wait; use for...of or Promise.all over map. For large fan-out, cap concurrency in batches or with a limiter so you do not exhaust the database pool or hit downstream rate limits.
  4. Cover reliability: put timeouts on outbound calls with AbortController, retry with backoff only for idempotent operations, use an idempotency key on POST, and respond 202 for slow work handed to a queue. Mention read-modify-write races across await points and fix them with a transaction or an atomic update.

Follow-up

  • If one promise in Promise.all rejects, what happens to the others that are already running?
  • When would you choose a sequential for...of loop over Promise.all, even though it is slower?
  • How would you stop a CPU-heavy task from blocking every other request on the server?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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?