');\n}).listen(0);\nconst base = http://localhost:${server.address().port};\n\nconst fail = [];\nconst check = (ok, msg) => { console.log((ok ? 'PASS ' : 'FAIL ') + msg); if (!ok) fail.push(msg); };\nconst browser = await chromium.launch();\nconst ctx = await browser.newContext();\nconst page = await ctx.newPage();\nconst text = async (sel) => { await page.waitForSelector(sel + '[data-ready]'); return page.textContent(sel); };\nconst visit = async (id) => { await page.goto(${base}/article/${id}); return [await text('#body'), await text('#comments')]; };\n\nawait visit(1); // first visit installs the worker\nawait page.evaluate(() => navigator.serviceWorker.ready);\nawait page.waitForFunction(() => navigator.serviceWorker.controller);\nconsole.log('online, first load :', await visit(1)); // now every request passes through the worker\nawait visit(2);\n\nawait fetch(base + '/__bump'); // the server publishes an update\nconst online = await visit(1);\nconsole.log('online, after edit :', online);\ncheck(online[0] === 'Article 1 v2' && online[1] === 'Comments: 20', 'online: the reader sees the updated article and the new comment count at once');\n\nawait fetch(base + '/__down'); // the network disappears\nconst off1 = await visit(1), off3 = await visit(3);\nconsole.log('offline, visited :', off1);\nconsole.log('offline, unvisited :', off3);\ncheck(off1[0] === 'Article 1 v2', 'offline: a previously visited article is readable, at the version last seen');\ncheck(off1[1] === 'Comments unavailable offline', 'offline: comments are never served stale; the page says they are unavailable');\ncheck(off3[0] === 'This article is not saved for offline reading.', 'offline: an unvisited article gets an honest message inside the cached shell');\nawait fetch(base + '/__up');\n\nawait fetch(base + '/__hang'); // the network stalls: requests are accepted and never answered\nconst hung = await page.evaluate(async () => {\n const t0 = performance.now();\n try { const r = await fetch('/api/article/1', { signal: AbortSignal.timeout(8000) }); return [(await r.json()).text, performance.now() - t0]; }\n catch { return ['no answer', performance.now() - t0]; }\n});\nawait fetch(base + '/__unhang');\nconsole.log('hanging network :', hung[0], hung[1] >= 2900 && hung[1] < 6000 ? '(after about 3 s)' : '(after ' + Math.round(hung[1]) + ' ms)');\ncheck(hung[0] === 'Article 1 v2' && hung[1] >= 2900 && hung[1] < 6000, 'a stalled network is cut off after about 3 s and the cached copy is served');\n\nfor (const id of [1, 2, 3, 4, 5]) await visit(id); // five articles read, cache limit is 3\nconst articleKeys = () => page.evaluate(async () => (await (await caches.open('articles-v1')).keys()).map((r) => new URL(r.url).pathname));\nconst keys = await articleKeys();\nconsole.log('article cache keys :', keys);\ncheck(keys.length === 3 && keys.every((k) => ['/api/article/3', '/api/article/4', '/api/article/5'].includes(k)), 'the article cache keeps only the 3 most recently stored bodies');\n\nawait visit(6); // the server answers 500 for article 6\nconst keysAfter500 = await articleKeys();\ncheck(keysAfter500.length === 3 && !keysAfter500.includes('/api/article/6'), 'a 500 response is never stored as the last copy');\n\nawait page.evaluate(() => caches.open('shell-v0').then((c) => c.put('/old', new Response('old deploy')))); // a cache left by an older deploy\nawait fetch(base + '/__newsw'); // sw.js is now byte-different, so the browser installs a new worker\nawait page.evaluate(() => navigator.serviceWorker.getRegistration().then((r) => r.update()));\nlet cleaned = false; // wait up to 10 s for the new worker to install and activate\nfor (let i = 0; i < 40 && !cleaned; i++) {\n cleaned = !(await page.evaluate(() => caches.keys())).includes('shell-v0');\n if (!cleaned) await page.waitForTimeout(250);\n}\nconst names = (await page.evaluate(() => caches.keys())).sort();\nconsole.log('caches after update:', names);\ncheck(cleaned && names.join() === 'articles-v1,shell-v1', 'the new worker\\'s activate step deletes the old deploy\\'s cache and keeps the two current ones');\n\nawait browser.close(); server.close();\nprocess.exit(fail.length ? 1 : 0);\n\nRun in the Playwright 1.48.2 container (Chromium, headless), one sample run prints:\n\nonline, first load : [ 'Article 1 v1', 'Comments: 10' ]\nonline, after edit : [ 'Article 1 v2', 'Comments: 20' ]\nPASS online: the reader sees the updated article and the new comment count at once\noffline, visited : [ 'Article 1 v2', 'Comments unavailable offline' ]\noffline, unvisited : [\n 'This article is not saved for offline reading.',\n 'Comments unavailable offline'\n]\nPASS offline: a previously visited article is readable, at the version last seen\nPASS offline: comments are never served stale; the page says they are unavailable\nPASS offline: an unvisited article gets an honest message inside the cached shell\nhanging network : Article 1 v2 (after about 3 s)\nPASS a stalled network is cut off after about 3 s and the cached copy is served\narticle cache keys : [ '/api/article/3', '/api/article/4', '/api/article/5' ]\nPASS the article cache keeps only the 3 most recently stored bodies\nPASS a 500 response is never stored as the last copy\ncaches after update: [ 'articles-v1', 'shell-v1' ]\nPASS the new worker's activate step deletes the old deploy's cache and keeps the two current ones\nexit=0\n\nReading the listing: SW is the worker's source, held in a string so the demo can serve it from /sw.js. install precaches three shell files; activate deletes any cache whose name is not on the allow-list (shell-v1, articles-v1); the fetch handler sends navigations to the cached /shell.html, shell files to the cache first, /api/article/ to networkFirst, and ignores /api/comments/, which therefore goes to the network. networkFirst tries the network (with the 3,000 ms race), stores a copy of every successful response with cache.put(request, fresh.clone()) (a response body can be read only once, so one copy goes to the cache and the other to the page), trims the cache to the newest 3 entries (cache.keys() lists entries in the order they were added, so the first ones are the oldest), and on failure answers from the cache or with a small offlineMiss JSON error. The server takes the place of a real outage: after a request to /__down it destroys every incoming connection, which the browser sees as a network failure, the same way an offline phone would. /__bump raises the version so the test can tell old content from new. The test file's helper visit loads an article page and waits for the data-ready marker the page sets after its fetches finish, so no check runs on a half-loaded page. Three additions exist only for the later checks: /__hang makes article requests stall, so the 3,000 ms race is what returns the cached copy (the page-side fetch is given an 8-second AbortSignal.timeout so a missing race fails cleanly instead of hanging the test); article 6 always answers 500, which the fresh.ok test keeps out of the cache; and /__newsw makes sw.js byte-different, so registration.update() installs a new worker whose activate step deletes the leftover shell-v0 cache that the test created to stand for an older deploy.\n\nWhat the output shows: after the server publishes an edit, an online reload returns Article 1 v2 and Comments: 20 straight away (network first, network only). With the server down, article 1 still opens from the cache at the version last seen, v2, comments say they are unavailable, and an article the reader never opened shows a message instead of a browser error page, because the shell itself is served from the precache. After five articles are read, the article cache holds only the newest three, since without a bound the cache grows with every page ever read. With the network stalled, article 1 is still answered from the cache after the 3 s timeout, a 500 for article 6 is shown as an error but not stored, and a new worker version removes the old deploy's cache while keeping the two current ones.\n\nCache lifecycle and pitfalls\n\nUpdating the worker. The browser treats a worker as new only if the script is \"byte-different\" from the installed one. Cache names carry a version (shell-v1), the install step fills the new shell, and the activate step deletes caches that are not on the allow-list, which web.dev names as the right moment to clear old caches. The demo serves sw.js with Cache-Control: no-cache, an HTTP header telling the browser to ask the server whether its stored copy is still current before reusing it. That is a different cache from Cache Storage: the HTTP cache decides whether the browser re-downloads the worker script itself, and Cache Storage holds the pages and data the worker serves. It is a safeguard, not what makes updates work: by default (updateViaCache: 'imports') browsers ignore the HTTP cache when checking the top-level worker script for updates, and consult it only for scripts the worker pulls in with importScripts(). The header on sw.js itself therefore matters only when a page registers the worker with updateViaCache: 'all'; for imported scripts the header has to be on those files.\nskipWaiting() and clients.claim(). The demo calls both so a first visit is controlled straight away. web.dev warns that skipWaiting() \"means that your new service worker is likely controlling pages that were loaded with an older version\", so a deploy that changes the shell and the API shape together can briefly mismatch. If that matters, wait for the user to close tabs or show an \"update available\" prompt.\nOnly successful responses are stored. The demo checks fresh.ok before cache.put, so a 500 error page is not saved as the \"last copy\".\nAuthenticated or personalised content must not go into a cache shared across accounts on one device. Key or clear it per user.\nStorage is finite. The bound of 3 here is for the demo. A real limit is a number of articles you choose, and eviction is the code's job.\nMisses are normal. An offline reader opening a page that was never visited needs a designed answer, as in the demo, not a blank page.\n\nRunning the code\n\nmkdir demo && cd demo && echo '{\"type\":\"module\"}' > package.json\nnpm i playwright@1.48.2 # save the listing above as sw-demo.mjs\ndocker run --rm -v \"$PWD\":/w -w /w mcr.microsoft.com/playwright:v1.48.2-jammy sh -c 'node sw-demo.mjs; echo exit=$?'\nadd -e BREAK=cachefirst to serve articles cache first: FAIL lines and exit=1"}},{"@type":"Question","name":"A mobile detail screen always needs an object but only sometimes needs its large related collection, such as comments. How would you design a single endpoint so the client can get the object with or without the collection? Show a sample request and response and explain the trade-offs.","acceptedAnswer":{"@type":"Answer","text":"Direct answer\n\nAdd an opt-in include query parameter to the detail endpoint. GET /v1/articles/7 returns the object plus a cheap commentCount. GET /v1/articles/7?include=comments&comments_limit=20 returns the same object plus a bounded first page of comments under an included key, with a link to fetch more. The collection is never embedded by default, never unbounded, and an unsupported include value is rejected with 400, not ignored.\n\nSample requests and responses\n\nRun in a Node 22 container with no dependencies (docker run --rm -v \"$PWD\":/w -w /w node:22 node include.mjs), include.mjs:\n\n// Run: node include.mjs (Node 22, no dependencies)\nimport http from 'node:http';\n\nconst article = { id: 7, title: 'Article 7', body: 'Lorem ipsum '.repeat(150) };\nconst comments = Array.from({ length: 12 }, (_, k) => ({ id: 700 + k, author: ['Ana', 'Ben', 'Chen'][k % 3], text: 'Nice post '.repeat(5).trim() }));\nconst SUPPORTED = new Set(['comments']);\n\nconst server = http.createServer((req, res) => {\n const u = new URL(req.url, 'http://x');\n const send = (s, o) => { res.writeHead(s, { 'content-type': 'application/json' }); res.end(JSON.stringify(o)); };\n if (u.pathname !== '/v1/articles/7') return send(404, {});\n const include = (u.searchParams.get('include') || '').split(',').filter(Boolean);\n const unknown = include.filter(i => !SUPPORTED.has(i));\n if (unknown.length) return send(400, { error: 'unsupported_include', values: unknown }); // reject, never ignore silently\n const out = { data: { ...article, commentCount: comments.length } }; // count is always present and cheap\n if (include.includes('comments')) {\n const raw = u.searchParams.get('comments_limit');\n const asked = raw === null ? 20 : Number(raw);\n if (!Number.isInteger(asked) || asked < 1) return send(400, { error: 'invalid_comments_limit' }); // 0, -1, 'abc', 2.5 are rejected\n const limit = Math.min(asked, 50); // server-side cap on the collection\n const items = comments.slice(0, limit);\n out.included = { comments: { items, next: items.length < comments.length ? /v1/articles/7/comments?after=${items.at(-1).id} : null } };\n }\n send(200, out);\n});\nawait new Promise(r => server.listen(0, r));\nconst base = http://127.0.0.1:${server.address().port};\nfor (const q of ['', '?include=comments&comments_limit=2', '?include=likes', '?include=comments&comments_limit=-1']) {\n const r = await fetch(base + '/v1/articles/7' + q); const t = await r.text();\n console.log(GET /v1/articles/7${q}\\n -> ${r.status}, ${Buffer.byteLength(t)} bytes);\n console.log(' ' + t.replace(/\"body\":\"[^\"]*\"/, '\"body\":\"...\"'));\n}\nserver.close();\n\nOutput (the long body is elided by the script's print step):\n\nGET /v1/articles/7\n -> 200, 1865 bytes\n {\"data\":{\"id\":7,\"title\":\"Article 7\",\"body\":\"...\",\"commentCount\":12}}\nGET /v1/articles/7?include=comments&comments_limit=2\n -> 200, 2114 bytes\n {\"data\":{\"id\":7,\"title\":\"Article 7\",\"body\":\"...\",\"commentCount\":12},\"included\":{\"comments\":{\"items\":[{\"id\":700,\"author\":\"Ana\",\"text\":\"Nice post Nice post Nice post Nice post Nice post\"},{\"id\":701,\"author\":\"Ben\",\"text\":\"Nice post Nice post Nice post Nice post Nice post\"}],\"next\":\"/v1/articles/7/comments?after=701\"}}}\nGET /v1/articles/7?include=likes\n -> 400, 50 bytes\n {\"error\":\"unsupported_include\",\"values\":[\"likes\"]}\nGET /v1/articles/7?include=comments&comments_limit=-1\n -> 400, 34 bytes\n {\"error\":\"invalid_comments_limit\"}\n\nWith the 12-comment data set the cap of 50 cannot be seen. Changing length: 12 to length: 60000 and requesting comments_limit=500 returns a page of 50 comments, while comments_limit=-1, 0 and abc all return the same 400. Without the integer check, -1 would reach slice(0, -1) and return every comment but the last.\n\nDesign choices\n\n1. Opt-in, default off. The screen that does not need comments pays nothing. commentCount is always returned, so the screen can show \"12 comments\" and a button without loading any.\n2. Bounded by the server. comments_limit must be a whole number of at least 1 (anything else is a 400) and is capped on the server (50 here) whatever the client asks. An \"include everything\" request on an article with 50,000 comments would otherwise become the new over-fetch. next is a link to the rest of the list as its own endpoint (a sub-resource). Its after=701 part is a cursor: a bookmark that says \"continue after the item with id 701\", which is the last comment already delivered. The app requests that URL when the user scrolls, and the server answers with the next comments after 701, so nothing is repeated or skipped even if new comments arrive meanwhile.\n3. A separate included section, not nesting inside data. The primary object keeps one stable shape with or without the include. A compound document is one response that carries the main resource plus related resources in a separate section; for example the article in data and its comments in included, rather than comments mixed into the article's own fields. This mirrors the compound-document idea in JSON:API, a public REST convention whose specification also says a server that does not support include must answer 400 Bad Request, and that it must not return resources nobody requested.\n4. Strict validation. include=likes returns 400 with the offending value. Silently ignoring it would let a typo ship and the screen would just show no likes.\n\nTrade-offs\n\nOption | Round trips | Bytes when comments are not needed | Cache behaviour\n\nAlways embed comments | 1 | Large (always paid) | One URL per article\nTwo endpoints (article, then comments) | 2 when needed, 1 otherwise | Minimal | Each URL cached on its own; comments can have a shorter lifetime than the article\nOpt-in include (this design) | 1 either way | Minimal | One cache entry per include combination\n\nIn the run above, the plain request is 1,865 bytes and the request with two embedded comments is 2,114 bytes. The 249-byte difference is the two comments plus their wrapper, and it grows with the limit.\n\nCache-key growth. Each distinct include and comments_limit value is a separate URL for a CDN (content delivery network) or HTTP cache. Offer a few fixed combinations (limit 20 only) so the hit rate stays high.\nDifferent change rates. If the article changes weekly and comments change by the minute, embedding ties their freshness together: the cached composite is only as fresh as its most volatile part. When comments are very volatile, prefer the two-endpoint option.\nLatency. With include, the server waits for the slowest of the two data sources before sending anything. If comments come from a slow service, make the client fetch them separately, so the article paints first.\nGraphQL alternative. A query can ask for article { title comments(first: 20) { text } } or leave comments out, which solves the same problem without a parameter convention, with the caching trade-offs of a POST endpoint.\n\nPitfalls\n\nLetting include accept arbitrary nested paths (comments.author.friends), which turns a detail endpoint into a graph traversal. Support a short fixed list.\nReturning included.comments: [] when the user is not authorized to see comments. Check authorization for included data the same way as for the primary resource. For example, if comments on a members-only article are visible only to subscribers, a non-subscriber requesting ?include=comments should get 403, or no included section with a clear reason, not an empty list that looks like \"no comments yet\".\nForgetting the count: without commentCount the client has to load the collection to know whether one exists."}},{"@type":"Question","name":"Write a minimal OpenAPI 3.1 specification (YAML) for a POST /orders endpoint on an e-commerce service. Include the request body schema (items: an array of objects with product_id and quantity), a 201 success response schema (order_id, total_amount), 400 and 401 error responses, and an example request and response embedded in the spec.","acceptedAnswer":{"@type":"Answer","text":"The spec needs a request body schema for the array of order items, a 201 response schema for the created order, and explicit 400 and 401 error responses, each with an embedded example so a reader can see the exact shape without inferring it from the schema alone.\n\nThe specification\n\nopenapi: 3.1.0\ninfo:\n title: Orders API\n version: \"1.0.0\"\npaths:\n /orders:\n post:\n operationId: createOrder\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required: [items]\n properties:\n items:\n type: array\n minItems: 1\n items:\n type: object\n required: [product_id, quantity]\n properties:\n product_id:\n type: string\n quantity:\n type: integer\n minimum: 1\n example:\n items:\n - product_id: \"sku_1001\"\n quantity: 2\n - product_id: \"sku_2044\"\n quantity: 1\n responses:\n \"201\":\n description: Order created\n content:\n application/json:\n schema:\n type: object\n required: [order_id, total_amount]\n properties:\n order_id:\n type: string\n total_amount:\n type: number\n example:\n order_id: \"ord_7f3a\"\n total_amount: 64.50\n \"400\":\n description: Validation error\n content:\n application/json:\n schema:\n type: object\n required: [error_code, message]\n properties:\n error_code:\n type: string\n message:\n type: string\n example:\n error_code: \"invalid_quantity\"\n message: \"quantity must be at least 1\"\n \"401\":\n description: Missing or invalid credentials\n content:\n application/json:\n schema:\n type: object\n properties:\n error_code:\n type: string\n example:\n error_code: \"unauthorized\"\n\nThis document was parsed and structurally checked with a YAML parser: it parses cleanly as valid YAML, declares openapi: 3.1.0, defines the /orders path with the post operation described above, and every example object matches the shape its sibling schema declares (the 201 example has both order_id and total_amount, matching the required list; the 400 example has both error_code and message).\n\nDesign decisions worth calling out\n\nitems requires minItems: 1, so an order with zero line items is rejected at the schema level rather than reaching business logic.\nquantity has minimum: 1, catching a zero-or-negative quantity the same way, at the contract layer.\nThe 400 response has a structured shape (error_code plus message), not a bare string, so a client can branch on error_code programmatically instead of pattern-matching on human-readable text.\n\nTrade-offs and pitfalls\n\nKeeping this spec minimal (one endpoint, three responses) makes it easy to read in an interview or a code review, but a production version would need to decide, and document, what OTHER error shapes exist (a 404 if referencing a nonexistent product_id, a 409 for a stock conflict) so the response schema doesn't quietly grow undocumented shapes over time as edge cases get patched in. A second real trade-off: embedding literal examples directly in the spec (as done here) is excellent for readability and for generating realistic mocks, but every example needs to be kept in sync with the schema by hand or by a linter; an example that drifts from its own schema is worse than no example, because it actively misleads a reader."}}]}

Lyft Staff Full-Stack Developer Interview Preparation Guide

Full-Stack Developer
Lyft
Staff
7 rounds
Updated 6/14/2026

Lyft's interview process for Staff-level Full-Stack Developers typically consists of a recruiter screening phase, followed by technical phone screens, and concluding with a comprehensive onsite loop. The process evaluates technical depth across full-stack domains (frontend, backend, infrastructure), system design expertise, architectural decision-making, leadership capability, and cultural alignment with Lyft's mission-driven engineering culture.

Interview Rounds

1

Recruiter Screening

2

Technical Phone Screen - Frontend & Full-Stack Fundamentals

3

Technical Phone Screen - Backend & System Design Fundamentals

4

Onsite - Technical Interview: Frontend Deep Dive

5

Onsite - Technical Interview: Backend & API Design

6

Onsite - System Design Interview

7

Onsite - Behavioral and Leadership Interview

Frequently Asked Full-Stack Developer Interview Questions

System Design Methodology and Trade-off AnalysisEasyTechnical
67 practiced

Explain the CAP theorem: what do consistency, availability, and partition tolerance mean, and why can a distributed system only keep two of the three guarantees once a network partition happens? Give one real system that favors consistency (CP) and one that favors availability (AP), and explain what each gives up.

Systematic Debugging and Root Cause AnalysisHardTechnical
31 practiced

You are given the following Java concurrency snippet. Identify the potential deadlock and explain why it can happen. Propose at least three different fixes or mitigations (code-level and architectural). Include how you would detect such a deadlock in production.

java
public class Account {
  private final Object lockA = new Object();
  private final Object lockB = new Object();

  public void transferTo(Account other, int amount) {
    synchronized (lockA) {
      // prepare
      synchronized (other.lockB) {
        // perform transfer
      }
    }
  }

  public void receiveFrom(Account other, int amount) {
    synchronized (other.lockA) {
      synchronized (lockB) {
        // apply receive
      }
    }
  }
}
JavaScript and TypeScript FundamentalsMediumTechnical
84 practiced

JSON.stringify returns an empty object for a Map or Set. Explain why, and design a way to serialise and restore data that contains them, including nested ones.

Mentoring and CoachingEasyTechnical
84 practiced

Set two SMART goals with someone you're mentoring who needs to grow in a specific area of their job. Walk through how you picked those goals and how you'd know they'd been met.

Query Optimization and Execution PlansEasyTechnical
92 practiced

Why is SELECT * considered a performance anti-pattern for production dashboards, ETL jobs, and large queries? Rewrite a wide, unfiltered SELECT * query to be production-safe and explain each dimension of the improvement (I/O, network transfer, index-only-scan eligibility).

RESTful API DesignMediumSystem Design
77 practiced

Design the REST API for a data-enrichment microservice that multiple downstream teams will call, needing to sustain 1,000 requests per second at a P95 latency target of 200 milliseconds. Specify the endpoints and request/response contract, your idempotency approach for retried writes, your error model, and how you version the contract as the enriched schema evolves. Sketch, at a high level, how you would validate the design can actually sustain that load.

Project Delivery and Execution OwnershipEasyBehavioral
30 practiced

Tell me about a time you proactively removed a blocker, technical, process, or people, that was standing between your delivery and shipping. How did you spot it, what concrete steps did you take (technical fixes, workarounds, tooling, or just coordinating with the right people), whether and when you looped in others or escalated, and what measurably changed in your delivery timeline or team's velocity as a result.

Frontend Performance and Rendering OptimizationMediumTechnical
67 practiced

A news site should show fresh content when the user is online but let people reread articles they already visited when offline. Which service worker caching approach do you pick for the page shell, article bodies and fast-changing parts like comments, and why?

Backend-for-Frontend and Client-Specific API DesignEasyTechnical
33 practiced

A mobile detail screen always needs an object but only sometimes needs its large related collection, such as comments. How would you design a single endpoint so the client can get the object with or without the collection? Show a sample request and response and explain the trade-offs.

API Contracts and Schema DesignMediumTechnical
53 practiced

Write a minimal OpenAPI 3.1 specification (YAML) for a POST /orders endpoint on an e-commerce service. Include the request body schema (items: an array of objects with product_id and quantity), a 201 success response schema (order_id, total_amount), 400 and 401 error responses, and an example request and response embedded in the spec.

Want to create your own tailored preparation guide using our deep research?

Get Started for Free

Interview-Ready Courses

Visual-first, interactive, structured learning paths

Browse Full-Stack Developer jobs

AI-enriched listings across hundreds of company career pages

Explore Jobs