← Openai Interview Insights

Openai·Software Engineer·Onsite - System Design / Architecture·Senior

SeniorPrefer not to say
Jul 2026

Summary

System design round at OpenAI for a software engineering role, focused entirely on building a real-time streaming chat UI in React. The question was deceptively deep and kept branching into concurrency, state management, and reliability edge cases I wasn't fully prepared for.

Questions Asked (10)

Q1

Walk through how you'd design and build a React chat interface where assistant responses stream in token by token, users can send new messages mid-stream, and multiple conversation threads can be active at once.

System DesignTechnical Trade-offs
Author's notes

This started as a React question and turned into a full system design conversation.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying requirements and constraints, then outline a high-level architecture that separates concerns: state management, streaming transport, and UI rendering. Walk through the data flow for a single message, then extend to concurrent streams and mid-stream user input, highlighting trade-offs and edge cases.

Pro tip: Emphasize idempotency and cancellation: use AbortController to cancel in-flight requests when users send new messages, and design message IDs to deduplicate streamed tokens. This shows you think about real-world reliability, not just happy paths.

1. Clarify Requirements and Constraints

Ask about expected scale (concurrent users, threads), latency requirements, and whether streaming is via WebSockets, SSE, or fetch streams. Confirm if messages need to be persisted and if there's a backend API contract.

2. Design State Management

Propose a normalized state shape: threads keyed by ID, each with an ordered list of messages; messages have status (pending, streaming, complete) and content. Use a state library like Redux, Zustand, or React Context + useReducer, ensuring efficient updates for streaming tokens.

3. Implement Streaming Transport

Choose a transport (e.g., SSE or WebSocket) and handle token chunks. For each assistant message, create a placeholder and append tokens as they arrive. Use AbortController to cancel streams when users send new messages or switch threads.

4. Handle Concurrent Threads and Mid-Stream Input

Allow multiple active streams by keying streams to thread IDs. When a user sends a message mid-stream, cancel the current stream (or let it finish in background) and start a new one. Update UI optimistically and reconcile with server responses.

5. Optimize Rendering and Edge Cases

Use React.memo, virtualized lists, and batched state updates to avoid re-rendering entire threads on each token. Handle errors, reconnections, and race conditions (e.g., out-of-order tokens) with sequence numbers or timestamps.

Key Points to Mention

  • Use of AbortController to cancel in-flight requests when users send new messages or switch threads.
  • Normalized state management to efficiently update streaming messages without re-rendering entire thread lists.
  • Transport choice: Server-Sent Events (SSE) for unidirectional streaming vs. WebSockets for bidirectional, and trade-offs.
  • Optimistic UI updates and reconciliation with server state to handle mid-stream user input.
  • Handling race conditions and out-of-order tokens with sequence numbers or timestamps.
  • Performance optimizations: React.memo, virtualized lists, and batching state updates for high-frequency token updates.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

Name two pieces of state that only exist while a stream is active and explain how and when you clean them up.

System DesignTechnical Trade-offs
Author's notes

Got AbortController immediately.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by defining the stream context (e.g., a real-time data pipeline or WebSocket connection) and name two transient state pieces like a buffer and a session token. Explain how each is created on stream start and cleaned up on stream end or error, emphasizing resource management and idempotency.

Pro tip: Mention that cleanup must be idempotent and happen in a finally block or equivalent to handle both normal termination and failures, showing you think about reliability.

1. Define the stream context

Briefly describe the stream scenario (e.g., a live data feed or chat session) to ground your answer in a concrete system.

2. Identify two transient state pieces

Name two specific pieces of state that only exist during the stream, such as an in-memory buffer and a session ID, and explain their purpose.

3. Explain creation and lifecycle

Describe when and how each state is initialized at stream start, and how it's used during the stream.

4. Detail cleanup triggers and methods

Specify when cleanup occurs (e.g., on stream end, error, or timeout) and how you release resources (e.g., clear buffer, revoke token).

5. Highlight reliability considerations

Emphasize idempotent cleanup, error handling, and avoiding resource leaks to demonstrate production readiness.

Key Points to Mention

  • Buffer for incoming data chunks
  • Session token or connection ID
  • Cleanup on stream completion, error, or timeout
  • Idempotent cleanup operations
  • Use of finally blocks or equivalent for guaranteed cleanup
  • Avoiding memory leaks and resource exhaustion

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

How do you make sure streamed tokens append correctly without flickering or overwriting content, especially when state updates are happening rapidly?

System DesignTechnical Trade-offs
Author's notes

Talked about functional setState updaters so you're always appending to the latest snapshot rather than a stale closure.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the scenario: streaming tokens from an API into a UI with rapid state updates. Then explain how you separate the streaming buffer from the rendered state, using techniques like batching, immutable updates, and keys to ensure smooth appends without flicker or overwrites.

Pro tip: Mention that you measure performance with React Profiler or similar tools to validate that your optimizations actually reduce re-renders and flicker, showing a data-driven approach.

1. Clarify the scenario and constraints

Ask about the tech stack (e.g., React, Vue), update frequency, and whether tokens arrive via WebSocket or SSE. This shows you tailor solutions to context.

2. Separate buffer from rendered state

Explain that you accumulate incoming tokens in a mutable buffer (e.g., ref or external store) and only commit to state at controlled intervals, avoiding a state update per token.

3. Batch and schedule updates

Use requestAnimationFrame, setTimeout, or a batching library to flush the buffer to state at most once per frame, aligning with the browser's render cycle to prevent flicker.

4. Ensure stable rendering with keys and immutable appends

Render tokens as a list with stable keys (e.g., index or token ID) and append immutably (e.g., [...prev, newToken]) so React reconciles efficiently without overwriting.

5. Optimize and verify

Use memoization (React.memo, useMemo) for token components and measure with profiling tools to confirm no unnecessary re-renders or layout shifts.

Key Points to Mention

  • Batching state updates to avoid a re-render per token
  • Using a mutable buffer (useRef) to accumulate tokens before committing to state
  • Scheduling updates with requestAnimationFrame to sync with browser paint
  • Immutable state updates and stable keys for list rendering
  • Memoizing token components to prevent re-renders of unchanged tokens
  • Measuring performance with React Profiler or browser dev tools

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

What can go wrong when multiple responses are streaming concurrently, and how do you prevent one stream from corrupting another's state?

System DesignTechnical Trade-offs
Author's notes

This is where I felt most confident.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by acknowledging that concurrent streaming introduces shared mutable state risks, then systematically walk through failure modes like interleaved writes, race conditions, and resource contention. Emphasize isolation techniques such as per-stream context, immutable data, and synchronization primitives, and tie them to real-world streaming architectures like SSE or WebSockets.

Pro tip: Mention that you'd use a correlation ID or stream token to tag every chunk and validate it at the consumer, which prevents cross-stream contamination even if the transport layer misroutes data. This shows you think beyond just locking and consider end-to-end integrity.

1. Identify shared state and failure modes

Enumerate where streams might share state (e.g., buffers, session objects, global caches) and describe concrete corruption scenarios like interleaved writes, stale reads, and race conditions.

2. Isolate stream state

Explain how to give each stream its own context—such as per-connection objects, thread-local storage, or immutable snapshots—so that one stream's data cannot bleed into another.

3. Synchronize access to shared resources

Describe appropriate synchronization for unavoidable shared resources: locks, semaphores, atomic operations, or actor-model mailboxes, and discuss trade-offs like contention and deadlock risk.

4. Validate and recover

Cover runtime checks like sequence numbers, checksums, or stream IDs to detect corruption, and outline recovery strategies such as stream termination, retry, or fallback to a clean state.

5. Test and monitor

Mention stress testing with concurrent streams, chaos engineering, and monitoring for anomalies (e.g., latency spikes, error rates) to catch corruption in production.

Key Points to Mention

  • Race conditions and interleaved writes to shared buffers or session state
  • Per-stream isolation using context objects, thread-local storage, or immutable data
  • Synchronization primitives (locks, semaphores, atomics) and their trade-offs
  • Stream identification and validation (correlation IDs, sequence numbers, checksums)
  • Backpressure and flow control to prevent resource exhaustion
  • Idempotency and recovery strategies for corrupted or failed streams

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q5

When would you use useRef versus useState in this streaming scenario, and what's your general rule for deciding?

System DesignTechnical Trade-offs
Author's notes

Render-affecting data goes in state or a reducer.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the streaming scenario (e.g., real-time data, frequent updates) and define the distinct roles of useRef (mutable value that doesn't trigger re-render) and useState (state that triggers re-render). Then, walk through concrete examples in the streaming context where each is appropriate, and conclude with a general rule based on whether the value affects rendering or needs to persist across renders without causing updates.

Pro tip: Emphasize that useRef is often used for values that are not part of the render output, such as timers, subscriptions, or previous values, while useState is for values that drive the UI. Mention that overusing useState for non-rendering values can lead to unnecessary re-renders and performance issues, especially in high-frequency streaming scenarios.

1. Clarify the streaming scenario

Ask or state assumptions about the streaming context: Is it real-time data? How frequent are updates? What is the UI impact? This sets the stage for trade-offs.

2. Define useRef and useState

Briefly explain that useRef returns a mutable object whose .current property can hold any value and does not trigger re-renders, while useState returns a stateful value and a setter that triggers re-renders.

3. Apply to streaming examples

Give specific examples: useRef for storing the latest stream chunk without re-rendering, managing subscriptions, or tracking previous values; useState for updating the UI with new stream data, such as displaying the latest message or progress.

4. Discuss trade-offs

Explain performance implications: useState causes re-renders which can be costly with high-frequency updates; useRef avoids re-renders but changes won't reflect in UI. Mention patterns like throttling or batching with useState.

5. State the general rule

Conclude with a rule: Use useState when the value is part of the render output and should trigger UI updates; use useRef when the value is not used in rendering, needs to persist across renders, and mutating it should not cause re-renders.

Key Points to Mention

  • useRef does not trigger re-renders; useState does.
  • useRef is for mutable values that persist across renders without causing updates (e.g., timers, subscriptions, previous values).
  • useState is for values that affect the rendered output and should cause re-renders when changed.
  • In streaming, high-frequency updates can cause performance issues if using useState; useRef can help avoid unnecessary re-renders.
  • Sometimes both are used together: useRef to store the latest value and useState to trigger UI updates at a controlled rate.
  • General rule: If the value is used in JSX or affects rendering, use useState; otherwise, use useRef.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q6

If the same network request gets sent twice due to a double-submit, retry, or StrictMode remount, how do you prevent a duplicate assistant message from appearing?

System DesignAPI & Integrations
Author's notes

Client-generated idempotency key (a UUID) sent with the request, server-side dedup as the real defense, and a client-side guard so a second response for the same key can't create a new message slot.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the root cause: duplicate requests can stem from client-side double-submits, network retries, or React StrictMode double-invocation in development. Then propose a layered solution: idempotency keys on the server to deduplicate, client-side guards like request deduplication and optimistic UI with unique message IDs, and proper handling of retries with exponential backoff and jitter. Emphasize that the best approach depends on the specific scenario and that you'd instrument and monitor to catch duplicates in production.

Pro tip: Mention that StrictMode double-invocation is intentional in development to surface side-effect bugs, so the fix should be in the code, not by disabling StrictMode. Also, highlight that idempotency keys should be generated client-side and stored with the request, and that the server should return the same response for duplicate keys to ensure consistency.

1. Identify the source of duplication

Determine whether the duplicate request is caused by user double-click, automatic retry logic, or React StrictMode's intentional double-invocation in development. Each requires a different mitigation.

2. Implement client-side safeguards

Disable the submit button after the first click, use a request deduplication library (e.g., Axios interceptors or a custom in-flight request map), and ensure effects are idempotent or cleaned up properly in StrictMode.

3. Use idempotency keys for server-side deduplication

Generate a unique key per logical request (e.g., UUID) on the client and include it in the request header. The server stores the key and returns the cached response for duplicates, preventing duplicate side effects.

4. Handle retries with backoff and jitter

For network retries, use exponential backoff with jitter to avoid thundering herd, and ensure retries are idempotent by reusing the same idempotency key.

5. Design UI for optimistic updates and deduplication

Assign a unique client-side ID to each message before sending, and when receiving responses, reconcile based on that ID to avoid rendering duplicates. Use a state management pattern that ignores duplicate responses.

Key Points to Mention

  • Idempotency keys: client-generated unique identifiers that the server uses to deduplicate requests and return consistent responses.
  • React StrictMode: double-invokes effects in development to catch bugs; solutions include using cleanup functions, AbortController, or refs to track in-flight requests.
  • Request deduplication: techniques like in-flight request maps or libraries (e.g., Axios interceptors) to cancel or ignore duplicate requests.
  • Optimistic UI with unique message IDs: assign a temporary ID to the message before sending, and replace it with the server ID upon response, ensuring no duplicates.
  • Retry strategies: exponential backoff with jitter, and ensuring retries are idempotent by reusing the same idempotency key.
  • Monitoring and logging: track duplicate request rates and idempotency key collisions to detect and fix issues in production.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q7

React 18 StrictMode mounts effects twice in development. How does that interact with opening a stream inside useEffect, and how do you make the setup and teardown idempotent?

System DesignTechnical Trade-offs
Author's notes

Return a cleanup function that aborts the stream, and make sure the server-side idempotency key means the second invocation doesn't open a second real connection or produce a second response.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Explain that React 18 StrictMode intentionally double-invokes effects in development to surface non-idempotent side effects, and that opening a stream in useEffect without proper cleanup leads to duplicate connections and resource leaks. Then describe how to make setup and teardown idempotent by ensuring each effect run has a corresponding cleanup that fully tears down the stream, and by using guards like AbortController or a 'cancelled' flag to prevent stale updates.

Pro tip: Emphasize that StrictMode double-invocation is a development-only diagnostic, not a bug, and that writing idempotent effects also improves resilience in production (e.g., Fast Refresh, concurrent rendering). Mention that you can verify idempotency by logging connection counts or using React DevTools to inspect effect runs.

1. Clarify StrictMode behavior

Explain that React 18 StrictMode mounts, unmounts, and remounts components in development to help detect side effects that aren't properly cleaned up. This means useEffect runs twice: setup, cleanup, setup again.

2. Identify the problem with streams

Opening a stream in useEffect without cleanup creates two concurrent streams, leading to duplicate data, memory leaks, and race conditions. The second setup may also conflict with the first if not properly isolated.

3. Design idempotent setup and teardown

Ensure that each effect run creates a self-contained stream instance and returns a cleanup function that fully closes that instance. Use AbortController or a local 'cancelled' flag to ignore events from stale streams.

4. Implement and verify

Write the effect with proper cleanup, then test in StrictMode to confirm only one active stream remains after the double-invocation. Use logging or DevTools to verify no leaks.

5. Discuss trade-offs and production implications

Note that while StrictMode only affects development, the patterns you use (idempotent effects) also prevent issues in production with Fast Refresh or future concurrent features. Mention alternatives like using a ref to track initialization if appropriate.

Key Points to Mention

  • React 18 StrictMode intentionally double-invokes effects in development to detect non-idempotent side effects.
  • Opening a stream in useEffect without cleanup leads to duplicate connections and resource leaks.
  • Idempotent setup means each effect run creates a new, independent resource, and cleanup fully tears it down.
  • Use AbortController or a 'cancelled' flag to prevent state updates from stale streams.
  • Cleanup functions should be symmetric to setup: if you open a stream, close it; if you subscribe, unsubscribe.
  • StrictMode double-invocation is a development-only diagnostic, but writing idempotent effects improves production resilience.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q8

The connection drops halfway through a streaming response. How do you handle recovery, and what do you need from the server contract to support it?

System DesignAPI & Integrations
Author's notes

Offered three options: resume from an offset (needs server support for a byte range or sequence number), restart from scratch with a new stream, or surface the partial message as-is with an error state.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the streaming protocol (e.g., SSE, WebSocket, HTTP chunked) and the nature of the drop (network, server, client). Then outline a recovery strategy that includes resuming from the last received token/byte using a cursor or offset, with idempotency and deduplication. Finally, specify the server contract requirements: resumable endpoints, sequence numbers, heartbeats, and error signaling.

Pro tip: Emphasize that recovery must be idempotent and that the server should support resuming from a client-provided cursor; this shows you think about correctness and efficiency, not just reconnecting.

1. Clarify the streaming context

Identify the streaming protocol (SSE, WebSocket, HTTP chunked) and the failure mode (network blip, server restart, client crash). This determines the recovery mechanism.

2. Design client-side recovery

On disconnect, the client should attempt to reconnect with exponential backoff and resume from the last successfully processed chunk using a cursor (e.g., sequence number or byte offset). Buffer unacknowledged data and handle duplicates.

3. Define server contract for resumability

The server must support a resumable endpoint that accepts a cursor and returns the stream from that point. It should include sequence numbers or offsets in each chunk, and provide a way to signal stream end or errors.

4. Ensure idempotency and consistency

Use idempotent operations and deduplication on the client to avoid processing the same chunk twice. The server should guarantee that resuming from a cursor yields exactly the missing data without gaps or duplicates.

5. Handle edge cases and monitoring

Address scenarios like cursor expiration, server-side state loss, and partial writes. Implement heartbeats to detect dead connections and log recovery attempts for observability.

Key Points to Mention

  • Resumable streaming with cursor/offset (e.g., Last-Event-ID for SSE, sequence numbers for WebSocket)
  • Idempotency and deduplication to handle at-least-once delivery
  • Exponential backoff with jitter for reconnection attempts
  • Heartbeats/keep-alives to detect connection drops promptly
  • Server-side support for resuming from a cursor and signaling stream end/errors
  • Client-side buffering and acknowledgment of processed chunks

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q9

Rendering on every token tanks performance for long message lists. How would you diagnose this and what techniques would you apply?

System DesignTechnical Trade-offs
Author's notes

React DevTools profiler to find what's re-rendering, then: buffer tokens and flush on an interval rather than per-token, memoize the static parts of the message list so only the actively streaming message re-renders, virtualize the list if it gets long.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by describing how you would measure and confirm the performance bottleneck, then systematically walk through rendering optimization techniques. Emphasize React-specific solutions like virtualization, memoization, and batching, and connect them to the trade-offs in a long message list scenario.

Pro tip: Mention that you would first reproduce the issue with a profiler and quantify the cost per token before optimizing, because premature optimization can lead to unnecessary complexity. Also, highlight that the best solution often combines virtualization with memoization and avoiding unnecessary re-renders at the component level.

1. Diagnose and Measure

Use React DevTools Profiler and browser performance tools to identify which components re-render on each token and measure the time spent. Confirm that rendering is the bottleneck, not network or state management.

2. Identify Root Causes

Determine why every token causes a re-render: e.g., state updates at a high level, lack of memoization, or inefficient list rendering. Check if the entire list re-renders or only the new message.

3. Apply Rendering Optimizations

Implement techniques like virtualization (react-window, react-virtualized), React.memo for list items, useCallback/useMemo to stabilize props, and key extraction. Consider windowing to render only visible messages.

4. Optimize State Management

Move state closer to where it's needed, use context selectors or state management libraries with fine-grained subscriptions, and batch updates to avoid multiple re-renders per token.

5. Validate and Iterate

Re-measure performance after each change, ensure no regressions, and consider trade-offs like added complexity vs. performance gains. Test with realistic message volumes.

Key Points to Mention

  • Virtualization/windowing to render only visible messages
  • React.memo and shouldComponentUpdate to prevent unnecessary re-renders
  • useCallback and useMemo to stabilize function and object references
  • Avoiding inline function definitions in render
  • Using keys correctly and avoiding index as key for dynamic lists
  • Batching state updates and using functional setState
  • Profiling with React DevTools and browser performance tools
  • Trade-offs: complexity, memory usage, and initial render time

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q10

If you moved the entire streaming state machine out of React component state, where would it live and what do you gain or lose compared to keeping it in useReducer?

System DesignTechnical Trade-offs
Author's notes

Said I'd put it in a custom hook backed by an external store (something like Zustand or a hand-rolled pub-sub).

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the scope of the streaming state machine and the constraints (e.g., real-time updates, cross-component sharing, persistence). Then compare external state managers (Redux, Zustand, XState, MobX) and non-React stores (custom event emitters, RxJS) against useReducer, focusing on trade-offs in complexity, performance, and testability. Conclude with a recommendation based on the specific needs of the streaming use case.

Pro tip: Emphasize that moving state out of React can reduce unnecessary re-renders and enable state sharing across components, but it introduces synchronization challenges and may require manual subscription management. Show that you consider the team's familiarity and long-term maintainability, not just technical purity.

1. Clarify the state machine's requirements

Identify what the streaming state machine manages (e.g., connection status, buffered chunks, playback position) and its constraints (e.g., high-frequency updates, need for persistence, cross-component access).

2. Evaluate external state solutions

Compare options like Redux, Zustand, XState, MobX, or a custom store (e.g., event emitter, RxJS) based on their fit for streaming state, including reactivity, performance, and dev tools.

3. Analyze gains and losses vs. useReducer

Discuss gains: decoupling from React, easier sharing, persistence, and testability. Losses: added complexity, potential over-engineering, loss of React's concurrent features, and need for manual subscription management.

4. Consider integration and migration

Explain how to integrate the external store with React (e.g., useSyncExternalStore, context) and how to migrate incrementally without disrupting the app.

5. Recommend based on trade-offs

Provide a clear recommendation, justifying it with the specific needs of the streaming state machine and the team's context, and acknowledge any remaining risks.

Key Points to Mention

  • Decoupling state logic from React components improves testability and reusability.
  • External stores can reduce re-renders by allowing selective subscriptions, unlike useReducer which re-renders the whole component tree.
  • State sharing across components becomes easier without prop drilling or context.
  • Persistence and time-travel debugging are more straightforward with external stores like Redux or XState.
  • Added complexity: need to manage subscriptions, potential memory leaks, and loss of React's built-in optimizations.
  • useReducer is simpler and sufficient for localized state, but may not scale for complex, shared streaming state.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.