iosNudge

Requirements

Chapter 3 of 8 4 min read

Why start here

The prompt is vague on purpose. "Design a chat app" can mean a hundred different apps. The interviewer wants to see if you ask before you build. Five minutes here saves twenty minutes of wrong design.

Your output is a short written list. It is your contract with the interviewer for the rest of the round.

Functional requirements

Functional requirements say what the user can do. Write them as user actions. Keep 3 to 5.

Chat app: functional
- See a list of conversations, newest first
- Open a conversation and scroll through old messages
- Send a text message
- See new messages without pulling to refresh
- See if a message is sent, delivered or read

Each functional requirement often becomes one method on a protocol later. That makes the list useful, not just a ritual:

protocol ChatService: Sendable {
    func conversations() async throws -> [Conversation]
    func messages(in conversationID: String, before cursor: String?) async throws -> [Message]
    func send(_ text: String, to conversationID: String) async throws -> Message
    func events() -> AsyncStream<ChatEvent>   // new messages, read receipts
}

Also write what is out of scope: "No group calls, no stickers, no end-to-end encryption." This protects your time.

Non-functional requirements for mobile

Non-functional requirements say how well the app must work. On mobile they drive most design choices. This is where iOS candidates shine. Go through the list below quickly. Keep only what matters for this product.

Offline

Ask: "Should the app work without a network? Read only, or also write?"

  • Read offline: save data on disk and show it first.
  • Write offline: put actions in a queue and send them later.

Use NWPathMonitor to react when the network changes:

import Network

let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
    let isOnline = path.status == .satisfied
    let isExpensive = path.isExpensive        // cellular data or a hotspot
    print("online: \(isOnline), expensive: \(isExpensive)")
}
monitor.start(queue: DispatchQueue(label: "network-monitor"))

Do not check the network before every request. Just send the request and handle the error. Use the monitor to update the UI and to retry when the network comes back.

Battery

  • Avoid polling (asking the server again and again). Use push or a WebSocket.
  • Group network calls together. Each call wakes the radio, and the radio uses a lot of power.
  • Do less when Low Power Mode is on.
if ProcessInfo.processInfo.isLowPowerModeEnabled {
    // Turn off video autoplay and pause image prefetching.
}

Memory

iOS stops apps that use too much memory, even in the foreground. Ask about the content: big images? video? very long lists? Then plan limits.

let imageCache = NSCache<NSURL, UIImage>()
imageCache.totalCostLimit = 50 * 1024 * 1024   // about 50 MB, then old images are removed

Slow or bad network

Many users are on slow or unstable networks. Plan small pages, smaller images on cellular, timeouts and retries.

let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 15        // seconds
config.waitsForConnectivity = true           // wait for a network instead of failing at once

let mediaConfig = URLSessionConfiguration.default
mediaConfig.allowsConstrainedNetworkAccess = false   // big downloads respect Low Data Mode

Accessibility

Mention it early. It is a strong signal of a mature engineer.

  • VoiceOver: every control has a clear label.
  • Dynamic Type: text grows with the user's font size.
  • Enough color contrast, and support for Reduce Motion.
Button(action: like) {
    Image(systemName: post.isLiked ? "heart.fill" : "heart")
}
.accessibilityLabel(post.isLiked ? "Unlike" : "Like")

Text(post.caption)
    .font(.body)   // a text style, so it scales with Dynamic Type

Privacy and security

Ask: "What data is sensitive?" Then say where it lives.

  • Tokens and passwords go in the Keychain, never in UserDefaults.
  • Files with personal data use data protection (encrypted while the phone is locked).
  • Ask for permissions (camera, location, contacts) only when the user needs the feature.
  • List the data you collect in the privacy manifest.
try data.write(to: fileURL, options: [.atomic, .completeFileProtection])

Other things to check quickly

  • Minimum iOS version. It decides if you can use SwiftUI, Observation or SwiftData.
  • Devices. iPhone only, or iPad and landscape too?
  • Localization. Many languages? Right-to-left languages like Arabic?
  • Analytics. Which events does the product team need?

Questions to ask

Do not ask everything. Pick 4 to 6 questions that change the design.

Users and scale

  • Who are the users? Which countries? (Slow networks? Old phones?)
  • How much data per user? Ten chats or ten thousand?

Product

  • Which features matter most for this round?
  • Is there an existing backend, or do I design the API too?

Freshness and offline

  • How fresh must the data be? Real-time, or a few minutes old?
  • Must it work offline? Read only, or also write?

Platform and team

  • Minimum iOS version? SwiftUI or UIKit?
  • How big is the team? (This decides how much to split into modules.)

Give a reason with each question. This shows the question matters:

"How fresh should messages be? If it must be real-time, I'll use a WebSocket. If a minute is fine, push notifications and pull-to-refresh are enough, and they use less battery."

Quick numbers

Mobile rounds need small estimates too. Do them out loud. They lead to real decisions.

Photo feed, one full-width image: 1080 x 1080 pixels
Decoded in memory: 1080 x 1080 x 4 bytes = about 4.7 MB
20 images in memory = about 93 MB  -> too much

A 130-point grid cell on a 3x screen needs 390 x 390 pixels
390 x 390 x 4 bytes = about 0.6 MB  -> 8x less. So we downsample.

One page = 20 posts x about 2 KB of JSON = about 40 KB. Fine on a slow network.

From requirement to decision

Every requirement should lead to a design decision later. Make the link clear:

RequirementDesign decision
Read offlineDisk cache in the repository, show cached data first
Send offlineOutbox queue saved on disk, sent when online
Real-time messagesWebSocket while the app is open, push when it is closed
Smooth scrollingDownsampled images, decoding off the main thread, prefetching
Slow networksCursor pagination with small pages, retry with backoff
PrivacyKeychain for tokens, file protection, no personal data in logs

Write it down

Your final list for a chat app might look like this:

Functional:     conversation list, read history, send text, live updates, read receipts
Non-functional: read offline, send offline (queue), real-time when open,
                low battery use, VoiceOver, messages protected on disk
Out of scope:   groups over 50 people, calls, media upload, encryption
Numbers:        about 200 conversations and 10,000 messages per user

Finish with a check-in: "Does this scope look right to you?" Then move on. Do not spend more than 5 to 7 minutes on this step.