Sequential Swift Order Sender with FIFO Ordering and a Three-Send Retry Limit
Company: Robinhood
Role: iOS Engineer
Category: Software Engineering Fundamentals
Difficulty: easy
Interview Round: Onsite
In an iOS coding round you receive the Swift file below. `NetworkManager.sendOrder` simulates sending an order to the backend: it prints the order it is sending, waits a random time of up to one second, and then calls either its success callback or its failure callback on the main queue, each with equal probability. Implement `OrderManager.placeOrder(_:)` without changing its signature or anything above the marked line; you may add any properties and helper methods to `OrderManager`.
```swift
import Foundation
class NetworkManager {
static let shared = NetworkManager()
// Simulates sending an order request to the backend
func sendOrder(order: Order, success: @escaping () -> Void, failure: @escaping () -> Void) {
print("sendingOrder: \(order.identifier)")
let requestDuration = Double.random(in: 0...1.0)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + requestDuration) {
let rand = Int.random(in: 0...1)
if rand == 0 {
print("success: \(order.identifier)")
success()
} else {
print("failure: \(order.identifier)")
failure()
}
}
}
}
public struct Order: Equatable {
let identifier: String
}
/*
Do not edit above this line
================================================ */
public class OrderManager {
/// **Do not change this method signature**
public func placeOrder(_ order: Order) {
// TODO: Implement placeOrder so that:
//
// 1. Orders are sent ONE AT A TIME.
// Do not send the next order until the current one has finished
// (finished = succeeded, or given up after 3 failures).
//
// 2. Orders are sent in the SAME ORDER placeOrder was called.
// placeOrder can be called again while an order is still in flight;
// the new order must wait its turn.
//
// 3. If an order FAILS, retry the SAME order.
//
// 4. If the same order fails 3 TIMES IN A ROW (3 sends total),
// give up on it and move on to the next order.
//
// Use NetworkManager.shared.sendOrder(order:success:failure:) to send.
// You may add any properties / helper methods to this class.
}
}
```
**Expected behavior.** The interviewer's examples for these four calls, made one after another:
```text
orderManager.placeOrder(Order(identifier: "SNAP"))
orderManager.placeOrder(Order(identifier: "GOOG"))
orderManager.placeOrder(Order(identifier: "HOOD"))
orderManager.placeOrder(Order(identifier: "AAPL"))
Correct: each order is processed sequentially
sendingOrder: SNAP
success: SNAP
sendingOrder: GOOG
success: GOOG
sendingOrder: HOOD
failure: HOOD
sendingOrder: HOOD
success: HOOD
sendingOrder: AAPL
success: AAPL
Correct: after failing an order three times in a row, skip to the next order
sendingOrder: SNAP
failure: SNAP
sendingOrder: SNAP
failure: SNAP
sendingOrder: SNAP
failure: SNAP
sendingOrder: GOOG
success: GOOG
sendingOrder: HOOD
success: HOOD
sendingOrder: AAPL
success: AAPL
Incorrect: not waiting for SNAP to finish before sending other orders
Incorrect: orders sent in a different order than placeOrder was called
Incorrect: not moving on after three failures in a row
```
```hint What must be remembered
Think about what the manager has to keep between calls while one order is in flight and new orders keep arriving, including how many times the current order has already been sent.
```
```hint Where sends start
Ask in which situations a new send is allowed to begin, and make sure no other code path can start one.
```
### Constraints and Clarifications
- Nothing above the marked line may change, including `NetworkManager` and `Order`, and `placeOrder` keeps its signature.
- An order is finished when it succeeds or when its third send fails. An order that fails twice and then succeeds is finished after three sends, as a success.
- Results arrive asynchronously on the main queue, so `placeOrder` cannot block while it waits for a result.
### Clarifying Questions
- Which thread or threads call `placeOrder`: only the main thread, or any thread?
- If two equal orders (the same identifier) are placed, should both be sent?
- Should a retry go out immediately, or after a delay?
- `placeOrder` returns nothing. Does the caller need to learn each order's final outcome some other way?
- What should happen to waiting orders if the `OrderManager` itself is released?
### What a Strong Answer Covers
- The state the manager keeps: waiting orders in arrival order, the order in flight, and its send count, with at most one send outstanding
- Sends that start only when an order arrives at an idle manager or when a result comes back, so orders placed during a send wait their turn
- Exactly three sends for an order that keeps failing, with the count reset for each new order
- Thread safety: all state confined to one thread or serial queue, and no blocking of the main queue that delivers the callbacks
- Callback robustness and lifetime: what the closures capture, and duplicate or late callbacks
- A deterministic test strategy despite the random delay and the random outcome
### Follow-up Questions
- A send sometimes never calls back. How do you add a timeout without a late callback corrupting the state?
- Add exponential backoff between retries. What changes, and what must stay the same?
- How would you report each order's final result to the caller?
- How would you allow two different orders in flight at once while orders with the same identifier still go out one after another?
Overview: An iOS concurrency exercise: implement a Swift OrderManager whose placeOrder sends orders through an asynchronous, randomly failing network call one at a time and in call order, retrying a failed order until it has failed three sends in a row. It tests callback-driven state machines, thread safety, retry accounting and robust handling of completion callbacks.
Read the full Robinhood iOS Engineer interview experience this question came from