Skip to content

    Frontend System Design

    The Same-Origin Policy and CORS

    Learn what an origin is, what the same-origin policy withholds, and how CORS lets a server opt in, so you can predict whether a cross-origin fetch will work.

    By Published 17 min read

    • frontend-system-design
    • cors
    • same-origin-policy
    • browser-security
    Share

    Flagship lessonintermediateTypeScript labFrontend System Design

    Before this: HTTP Methods, Status Codes and Headers a Front End Relies On (coming soon)

    What you'll learn: By the end of this lesson you will be able to say what an origin is and compare two of them, explain what the same-origin policy lets a page do and what it withholds, follow a cross-origin request through the browser's CORS check step by step, tell a simple request from one that needs a preflight, and predict why a cross-origin fetch() failed from the response headers alone. A small TypeScript model of the rules lets you check your predictions.

    Picture an office building that houses several companies. A mail room serves everyone. Any employee can hand it a letter for another company, and the letter is delivered. But when the reply comes back, the mail room hands it over only if the company that wrote it attached a note saying "replies may be shown to this company". Without the note, the reply is thrown away unopened, even though the other company read your letter and did the work.

    A browser treats a cross-origin fetch() the same way. The request usually goes out. Whether your script may read the answer depends on a note the server attached to it. That note is CORS, and the rule that makes it necessary is the same-origin policy.

    The running example is a shop's product page at https://shop.example whose script loads products from https://api.shop.example/v1/products. To a visitor that is one site; to the browser it is two origins. (The .example names are reserved for documentation.)

    The core idea

    Hold on to three statements.

    1. An origin is a scheme, a host and a port. Two URLs are same-origin only if all three match. Path, query and fragment are ignored.
    2. By default a page may send requests to other origins but may not read the responses. This is the same-origin policy. It protects the user's signed-in sessions: a page you happen to visit cannot read your data from a site where you are logged in.
    3. CORS is the server's way to opt in. The server names, in response headers, who may read the response, and the browser enforces the answer. It does not protect the server: curl ignores CORS entirely.

    This lesson follows the HTML Standard (what an origin is), the Fetch Standard (the CORS protocol) and RFC 6454 (the web origin concept). The next lesson, CORS in Practice: Preflights, Credentials and Debugging, goes deeper into preflights, cookies and debugging.

    What an origin is

    The HTML Standard defines an origin as either an opaque origin or a tuple origin: a scheme, a host, a port, and a domain field that is normally empty. RFC 6454 gives the same grouping for URIs: the same scheme, host and port. Scheme is included because otherwise http://shop.example and https://shop.example would not be isolated from each other.

    Two tuple origins are same origin when their schemes and ports are identical and their hosts are equal. A missing port is replaced by the scheme's default (80 for http, 443 for https), so https://shop.example and https://shop.example:443 are the same origin. The table compares URLs with the page https://shop.example/products; the lab's tests assert every row.

    URL Same origin? Why
    https://shop.example/cart?step=2#pay yes path, query and fragment are ignored
    https://SHOP.example:443/ yes host is lower-cased, 443 is the default for https
    https://user:secret@shop.example/ yes user information is not part of the origin
    http://shop.example/ no different scheme
    https://shop.example:8443/ no different port
    https://api.shop.example/ no a subdomain is a different host
    http://localhost:3000 vs http://127.0.0.1:3000 no two names for one machine are two hosts

    Figure 1. Which of these origins can the page read?

    A page on shop.example and the origins it talks toPage script to Same-origin API (readable); Page script to Subdomain API (only if it opts in); Page script to Another site (only if it opts in)Page script: shop.examplePage scriptshop.exampleSame-origin API: shop.example/apiSame-originAPIshop.example/apiSubdomain API: api.shop.exampleSubdomain APIapi.shop.exampleAnother site: partner.exampleAnother sitepartner.examplereadableonly if it opts inonly if it opts in

    How to read this: only the top arrow stays inside one origin. The other two cross an origin boundary, so the page can read their responses only when those servers opt in with CORS headers.

    Opaque origins and the string "null"

    Some contexts have no meaningful scheme, host and port. A document made from a data: URL is one (MDN says it gets a new, empty security context), and MDN says modern browsers usually treat file:/// pages as opaque. Sandboxed frames are another common source. An opaque origin is an internal value whose only operation is equality: a freshly created one equals only itself. When it has to be written down, for example in an Origin header, it serialises as the string null. That surprises people the first time a server log shows Origin: null.

    Site is not origin

    The HTML Standard also defines a site: a scheme plus a registrable domain (the part a person can register, such as shop.example). https://shop.example and https://api.shop.example are the same site and different origins. The standard says same-origin checks are preferable to same-site ones, and CORS follows origins. A legacy feature, document.domain, let sibling subdomains agree to be treated as one origin; the standard says to avoid its setter because it undermines the same-origin policy, and that it is being removed. Use postMessage() or a MessageChannel to talk across origins.

    What the same-origin policy withholds

    The policy is less strict than "pages cannot talk to other origins". MDN sorts cross-origin interactions into three groups.

    Interaction Default Examples
    Writes typically allowed links, redirects, form submissions
    Embedding typically allowed <img>, <script src>, <link rel="stylesheet">, <iframe>, media
    Reads typically disallowed reading a response through fetch(), reading another document's DOM

    Web Storage and IndexedDB are separate for each origin. References to a window from another origin give very limited access to its Window and Location objects; window.postMessage is the sanctioned channel.

    The reason for the pattern is the browser's ambient credentials: it attaches the user's cookies for the destination automatically. A page on evil.example can make your browser request https://bank.example/account, cookies and all. The policy does not stop that request from leaving. It stops evil.example from reading the reply. So it protects the confidentiality of responses, not the server from unwanted requests: a state-changing form post is still delivered, which is why servers need separate defences against cross-site request forgery (see System Design: Security).

    How CORS opens the door

    Cross-Origin Resource Sharing is a set of HTTP headers, defined in the Fetch Standard, that indicates whether a response can be shared cross-origin. The browser works in four moves.

    1. The page calls fetch() with a cross-origin URL. The default mode of a Request made with the constructor is cors.
    2. The browser sends the request with an Origin header holding the page's serialised origin (scheme, host, port if non-default, no path). Script cannot set this header.
    3. The server answers. If it wants the page to read the answer, it includes Access-Control-Allow-Origin (ACAO): the literal origin from the request, or *.
    4. The browser runs the CORS check on the response. If it passes, the script gets the response. If not, it gets an error and no response.

    Figure 2. A simple cross-origin GET, with and without the opt-in

    sequenceDiagram accTitle: A simple cross-origin GET and the CORS check accDescr: The page script calls fetch, the browser sends the request with an Origin header, the server answers with or without Access-Control-Allow-Origin, and the browser either hands the response to the script or reports a network error. participant S as Page script participant B as Browser participant A as API server S->>B: fetch("https://api.shop.example/v1/products") B->>A: GET /v1/products
    Origin: https://shop.example A-->>B: 200 OK
    Access-Control-Allow-Origin: https://shop.example B->>B: CORS check passes B-->>S: Response (readable) Note over A,B: If the header is missing or wrong, the request was still sent,
    but the browser withholds the response. B--xS: error, no details

    How to read this: the request always reaches the server first. The browser decides afterwards, from the response headers, whether the script may see the answer. The last arrow is what the script gets when the check fails.

    The CORS check, step by step

    The Fetch Standard's "CORS check" has five steps, and the lab implements them in order.

    1. Read Access-Control-Allow-Origin. If there is none, fail.
    2. If the request does not include credentials and the value is *, succeed.
    3. Otherwise the value must equal the request's serialised origin exactly, or fail.
    4. If the request does not include credentials, succeed.
    5. Otherwise Access-Control-Allow-Credentials must be exactly true (case-sensitive) to succeed.

    Figure 3. The CORS check as a decision

    flowchart TD accTitle: The five steps of the CORS check accDescr: A decision flow. No Access-Control-Allow-Origin header fails. A wildcard value passes when the request has no credentials. Otherwise the value must equal the request origin exactly. Requests without credentials then pass. Requests with credentials also need Access-Control-Allow-Credentials set to true. A["Read Access-Control-Allow-Origin"] --> B{"Header present?"} B -->|"no"| X["Fail"] B -->|"yes"| C{"Value is *
    and no credentials?"} C -->|"yes"| OK["Pass"] C -->|"no"| D{"Value equals the
    request origin exactly?"} D -->|"no"| X D -->|"yes"| E{"Request includes
    credentials?"} E -->|"no"| OK E -->|"yes"| F{"Allow-Credentials
    is exactly true?"} F -->|"yes"| OK F -->|"no"| X

    How to read this: follow one response from the top. The only pass without a literal match is a wildcard on a request without credentials. The lower half is about cookies and other credentials.

    For a simple request, one header with one value decides. The match is byte for byte against the serialised origin, which has no trailing slash: the Fetch Standard's own table shows https://rabbit.invalid/ failing where https://rabbit.invalid passes. It is not a list or a pattern, so https://*.shop.example matches nothing, and one header cannot name two origins. A server that supports several origins reads the request's Origin, checks it against its own allowlist and echoes that one back, which matters for caches (below).

    If the response reaches the browser with two Access-Control-Allow-Origin headers, say one from the application and one from a proxy, the browser reads one combined value (https://shop.example, https://shop.example) that equals no origin. MDN lists "multiple Access-Control-Allow-Origin not allowed" among Firefox's CORS errors.

    Simple requests and preflighted requests

    Not every cross-origin request is sent straight away. For requests "more involved than what is possible with HTML's <form> element", the Fetch Standard has the browser first send a CORS-preflight request: an OPTIONS request asking whether the real request is acceptable. The usual explanation is that a form could already send a cross-origin GET or POST with a form-like body before CORS existed, so those add no new capability. Anything beyond that is new, and a server that predates CORS has never been asked about it.

    A request is simple (no preflight) when all of these hold, as MDN and the standard list them:

    • The method is GET, HEAD or POST.
    • The only headers the script set are CORS-safelisted request headers: Accept, Accept-Language, Content-Language, Content-Type, and Range (a single range with a start).
    • Content-Type, if set, is application/x-www-form-urlencoded, multipart/form-data or text/plain.
    • No upload listeners on an XMLHttpRequest, and no ReadableStream body.

    A safelisted value may be at most 128 bytes, and the standard caps the combined safelisted values at 1,024 bytes. Anything else triggers a preflight. The two surprises that catch most teams: Content-Type: application/json is not safelisted, and an Authorization header never is.

    Figure 4. Which path does a cross-origin fetch take?

    flowchart TD accTitle: The path a fetch takes in the browser accDescr: A decision flow. A same-origin request needs no CORS. In no-cors mode only simple requests are allowed and the response is opaque. In cors mode a request with a safelisted method and headers is sent directly and gets the CORS check. Any other request gets a preflight first. A["fetch() called"] --> B{"Same origin
    as the page?"} B -->|"yes"| S["Sent. Script reads the response."] B -->|"no"| C{"mode?"} C -->|"no-cors"| N["Only simple requests.
    Response is opaque."] C -->|"cors (default)"| D{"Safelisted method and
    safelisted headers only?"} D -->|"yes"| E["Send the request.
    CORS check on the response."] D -->|"no"| P["Send a preflight (OPTIONS) first.
    Real request only if it passes."]

    How to read this: the default path ends in the CORS check. A preflight is an extra round trip before it, covered in the next lesson. no-cors is the escape hatch for requests whose answer you never read.

    no-cors and credentials, briefly

    A request with mode: "no-cors" skips the CORS check but pays for it: only GET, HEAD or POST with the no-CORS-safelisted headers, and an opaque response whose headers and body JavaScript cannot see (MDN). It is still sent.

    Cookies and HTTP authentication are credentials. For fetch() the credentials option is omit, same-origin (the default) or include, so a cross-origin request carries no cookies by default. With include, steps 2 and 5 of the check tighten: * no longer works and the response must carry Access-Control-Allow-Credentials: true. The next lesson covers this and the wildcard rules for the other CORS headers.

    Three illustrations

    Illustration 1 (minimal): one header makes a read succeed

    The page at https://shop.example runs:

    // Runs on a page served from https://shop.example
    const response = await fetch("https://api.shop.example/v1/products");
    const products = await response.json();

    The browser sends (other headers omitted):

    GET /v1/products HTTP/1.1
    Host: api.shop.example
    Origin: https://shop.example

    If the API answers with the header below, the script receives the response. Access-Control-Allow-Origin: * would also pass, because the request carries no credentials. With no ACAO header the response is withheld.

    HTTP/1.1 200 OK
    Content-Type: application/json
    Access-Control-Allow-Origin: https://shop.example

    Illustration 2 (realistic): a product page with a cart that uses cookies

    The product page loads two things from api.shop.example: a public product list and the visitor's cart, which depends on a session cookie. The script sets credentials: "include" on the cart call only.

    Call Credentials A correct response needs
    GET /v1/products default: no cookie crosses origins Access-Control-Allow-Origin: * or the exact origin
    GET /v1/cart include Access-Control-Allow-Origin: https://shop.example and Access-Control-Allow-Credentials: true

    The common mistake is to put * on every route "to make CORS work". On the cart route the browser refuses it. The fix is the exact origin plus the credentials header, not a looser wildcard.

    Illustration 3 (failure): the right origin, still blocked

    Two failures that read as "the header is there but it does not work". The server sends Access-Control-Allow-Origin: https://shop.example/ with a trailing slash, which is not a serialised origin; or a proxy and the application both add the header, so the browser sees https://shop.example, https://shop.example. In both cases the network tab shows a 200 and real data, while the script gets an error with no details. MDN says the specifics are not available to JavaScript and "the only way to determine what specifically went wrong is to look at the browser's console". That is why the lab prints why each case failed.

    Build it: predict the browser's decision

    Goal. Compare origins, and for a table of cross-origin fetches predict whether the request is sent, whether the script can read the response, and why. Check your predictions against a model of the Fetch Standard's CORS check.

    Setup. The lab is in the course's example workspace. It needs Node.js (captured on Node 26.12.1, which runs TypeScript directly) and pnpm. It has no DOM and no network, and it models the standard's algorithm, not any one browser.

    cd examples/frontend
    pnpm install
    pnpm --filter ./labs/origin-and-cors-model test   # 60 tests
    cd labs/origin-and-cors-model
    node src/demo.ts

    Steps.

    1. Predict which URLs in the output below are same-origin before you read the verdict.
    2. For scenarios 2 to 10 in the full output, write down sent or not, readable or not, and the reason. Then compare.
    3. Run the tests. One reproduces the credentials table the Fetch Standard publishes.
    4. Read corsCheck in src/cors.ts: it is the five steps of Figure 3 in order.

    The origin comparison is two short functions:

    export function sameOrigin(a: Origin, b: Origin): boolean {
      if (a.kind === "opaque" || b.kind === "opaque") {
        return a.kind === "opaque" && b.kind === "opaque" && a.id === b.id;
      }
      return a.scheme === b.scheme && a.host === b.host && a.port === b.port;
    }
    
    export function serializeOrigin(origin: Origin): string {
      if (origin.kind === "opaque") return "null";
      const isDefault = DEFAULT_PORTS[origin.scheme] === origin.port;
      return `${origin.scheme}://${origin.host}${isDefault ? "" : `:${origin.port}`}`;
    }

    Expected output, the first part of the file, captured from a real run (Node 26.12.1, 2026-11-08):

    == Part 1: is it the same origin as the page?
    page: https://shop.example/products
    page origin: https://shop.example
    same   https://shop.example/cart?step=2#pay
           origin https://shop.example - path, query and fragment are ignored
    same   https://SHOP.example:443/
           origin https://shop.example - host is lower-cased; 443 is the default port of https
    other  http://shop.example/
           origin http://shop.example - different scheme
    other  https://shop.example:8443/
           origin https://shop.example:8443 - different port
    other  https://api.shop.example/
           origin https://api.shop.example - different host (a subdomain is another origin)
    same   https://user:secret@shop.example/
           origin https://shop.example - user information is not part of the origin
    other  data:text/plain,hello
           origin null - data: URLs get an opaque origin
    other  file:///tmp/page.html
           origin null - file: is opaque in this model (browsers decide)
    opaque origin serialises as "null" and equals itself: true, equals another opaque origin: false

    Some of the scenarios that follow (the complete file is examples/frontend/golden/origin-and-cors-model.txt):

    2. Cross-origin GET, server sends no CORS headers
       path: simple
       outcome: blocked | request sent: yes | script can read response: no | reason: missing-allow-origin
       The response has no Access-Control-Allow-Origin header. The request was already sent; only the response is withheld.
    5. Allow-Origin has a trailing slash
       path: simple
       outcome: blocked | request sent: yes | script can read response: no | reason: origin-mismatch
       Access-Control-Allow-Origin is "https://shop.example/" but the request origin is "https://shop.example". The request was already sent; only the response is withheld.
    7. Cookies requested (credentials: include), Allow-Origin: *
       path: simple
       outcome: blocked | request sent: yes | script can read response: no | reason: wildcard-with-credentials
       The request includes credentials, so Access-Control-Allow-Origin cannot be *. The request was already sent; only the response is withheld.
    10. JSON POST (Content-Type: application/json)
       path: preflighted
       outcome: needs-preflight | request sent: no | script can read response: no | reason: preflight-required
       A preflight OPTIONS request goes first (method is safelisted; unsafe headers: content-type).

    Dwell on scenario 2: request sent: yes, script can read response: no. A blocked response is not a prevented request.

    Break it. In src/cors.ts, in corsCheck, change if (credentials !== "include" && acao === "*") to if (acao === "*"). The model now accepts a wildcard even for a request with cookies. Three tests fail (the credentials-table row, the reason-per-failure test and the shipped-scenarios test), because the standard says Access-Control-Allow-Origin cannot be * when the credentials mode is include. Revert it.

    Stretch. Add the "same site" comparison from the HTML Standard (pass in a list of registrable domains as data), or the rule for which requests carry Origin: CORS requests, and any request whose method is neither GET nor HEAD.

    Choosing how the page reaches the API

    CORS is one of several ways to connect a page to an API on another origin.

    Approach What it gives you What it costs
    CORS with an allowlist (echo the match, add Vary: Origin) Direct calls, cookies possible, no extra hop Server configuration, preflights for JSON, caches must vary on Origin
    CORS with * Simple and cache-friendly; fine for public data No cookies; any page can read whatever the API serves without credentials
    Same-origin proxy (/api/... forwarded by the page's server) No CORS at all One more hop to run and secure
    Server-side fetch Secrets stay on the server Latency; the page sees only what your server exposes
    no-cors Fire-and-forget requests No data comes back

    The Fetch Standard offers a test for the wildcard row: if a resource cannot be fetched by an arbitrary machine with curl, do not put * on it; if it can, * reveals nothing that was not already public. Treat it as guidance and judge each resource.

    The other side of this topic

    On the back end the same headers are written by the server: choosing the allowlist, reading Origin, answering OPTIONS, and making proxies and caches agree. A later back-end lesson on CORS server configuration covers that half. The front-end skill is knowing which request your page really sends, because that decides what the server must answer.

    In practice: a dashboard whose API moved to its own subdomain

    NOTE

    This is a realistic scenario, not a case history. It is a composite built from common production patterns. It describes no specific company or product, and every number in it is an illustrative assumption.

    The situation

    A shop's staff dashboard at https://dashboard.shop.example used to call its APIs on the same origin. A platform change moves the product API to https://api.shop.example so it can be scaled and cached separately, so the calls are now cross-origin. A staging build of the dashboard at https://staging-dashboard.shop.example uses the same API.

    Starting architecture

    Figure 5. After the move: two front ends, one API behind a cache

    Two dashboards on different origins call one API origin through a shared cacheProduction to Shared cache (GET /v1/products); Staging to Shared cache (GET /v1/products); Shared cache to Product API (on a miss)API ORIGINProduction: dashboardProductiondashboardStaging: staging-dashboardStagingstaging-dashboardShared cache: in front of the APIShared cachein front of theAPIProduct API: api.shop.exampleProduct APIapi.shop.exampleGET /v1/productsGET /v1/productson a miss

    How to read this: both dashboards are separate origins reading the same URL. The cache in front of the API may answer either one with a stored response.

    Options considered

    Option What it fixes What it costs Risk
    Access-Control-Allow-Origin: * Every dashboard can read it Nothing to configure Fails once cookies are needed
    Allowlist both origins, echo the match, send Vary: Origin Known origins only; works with cookies A list to maintain A forgotten Vary, an unlisted preview URL
    Same-origin proxy on the dashboard's host No CORS A new service One more hop; separate staging path

    The decision

    Field Decision record
    Context Two dashboard origins read an API behind a shared cache; cookies may be needed later.
    Decision Allowlist the two origins, echo the matching one, always send Vary: Origin.
    Consequences, good Known origins only; works with credentials; each origin gets a response made for it.
    Consequences, bad An allowlist to keep current; a lower cache hit rate because responses vary by origin.
    Revisit when The list of front-end origins outgrows a hand-kept list, or a proxy path becomes simpler.

    Rollout and verification

    1. Test every origin that will call the API, not only production, and write down the expected header for each.
    2. Read the raw response headers at the cache and at the API: exactly one Access-Control-Allow-Origin, and Vary: Origin present.
    3. Request the same URL from two origins in a row. Both must succeed; this catches a missing Vary, and error responses (401, 500) need the header too.

    The 2 a.m. failure

    A week after rollout, the production dashboard shows an empty product table for a minute at a time, then recovers. The console says Access-Control-Allow-Origin is https://staging-dashboard.shop.example but the page's origin is https://dashboard.shop.example.

    The cause is the cache. Caching was enabled on the product route that week, and one layer did not forward Vary: Origin. A staging test run filled the cache first, so for the rest of the entry's freshness lifetime production received a stored response made for staging. The lab reproduces this in Part 3 of its output, with an illustrative 60-second freshness.

    First three checks: (1) request the URL with curl -H "Origin: https://dashboard.shop.example" and read the raw Access-Control-Allow-Origin and Vary; (2) repeat with the staging origin and compare; (3) see whether Vary: Origin exists at the API but is missing after the cache. Mitigation: purge the route and turn caching off, or vary on Origin at the cache. Follow-up: add the two-origin test to the release checklist, add a synthetic check per origin, and document that any cache in front of a CORS route that echoes the origin must key on Origin.

    Architect's notes

    • Decide early who owns the CORS headers. One layer (application, gateway or proxy) should write them; two writers produce duplicates.
    • Prefer an allowlist of exact origins to * for anything with cookies, and to reflecting whatever Origin arrives, which grants every site access.
    • Treat Vary: Origin as part of the contract whenever the response depends on the request's origin.
    • Keep CORS out of your security model. It governs what browsers let pages read. Authenticate and authorise on the server.

    Common mistakes

    • Treating a CORS error as a server error. The request usually succeeded; the browser withheld the response. Read the network tab and console together.
    • Putting * on routes that need cookies. The browser refuses it.
    • Adding a trailing slash, a path or a pattern to Access-Control-Allow-Origin. It must be one serialised origin.
    • Sending the header from two layers. Duplicates combine into an invalid value.
    • Assuming Content-Type: application/json is a simple request. It triggers a preflight.
    • Forgetting error responses. A 401 or 500 without the header looks like a CORS failure.

    Key takeaways

    • An origin is scheme, host and port. Path, query, fragment and user information do not count, and a missing port means the scheme's default.
    • Opaque origins (data: documents, often file: pages) equal only themselves and serialise as null.
    • The same-origin policy typically allows cross-origin writes and embedding, disallows reads, and keeps storage separate per origin.
    • A blocked cross-origin response does not mean the request was not sent.
    • CORS is the server's opt-in: Access-Control-Allow-Origin must be * (without credentials) or exactly the requesting origin.
    • With credentials the response also needs Access-Control-Allow-Credentials: true, and * is refused.
    • Simple requests are sent directly. application/json bodies and Authorization headers get a preflight.
    • no-cors requests are sent but their responses are opaque.
    • A response that depends on Origin needs Vary: Origin, or a cache may serve one origin's response to another.

    Practice exercises

    Exercise 1 — Starter (20 min): Pick six URLs (a port, an http URL, a subdomain, a localhost/127.0.0.1 pair), predict same-origin or not, then check with originOf and sameOrigin.

    Exercise 2 — Intermediate (45 min): Add three scenarios to src/scenarios.ts (for example Access-Control-Allow-Origin: null from an opaque origin), write your prediction first, then run the demo.

    Exercise 3 — Intermediate (1 hour): Serve a page on http://localhost:3000 and an API on http://localhost:4000. Send a cross-origin GET with no CORS headers, then the exact origin, then *, and record the network tab and console each time.

    Exercise 4 — Advanced (2 hours): Write a design note for a page and an API on a sibling subdomain: choose an allowlist, a wildcard or a proxy, list the preflighted requests, and say what you would test before launch.

    In this course:

    Elsewhere on the site:

    Official documentation

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.

    Get new articles by email

    Free. We email a confirmation link first, and you can unsubscribe from any email.

    Join engineers building production AI systems

    Comments