Articles
iOS Background Task Interview Questions: Scheduling, Expiration, and Completion
Practice iOS background task interview questions with a scheduling choice, expiration trace, and completion-once checklist grounded in Apple guidance.
An iOS background-task interview answer should begin with a scheduling contract, not a timer. Submitting a task request does not promise that iOS will launch it at a particular minute. Once the system does launch it, the app must handle expiration, stop or checkpoint work, and report completion. The best answer separates work that briefly continues after the app leaves the foreground from refresh work that the system may launch later.
Official Apple facts in this guide come from Apple's Background Tasks and UIKit documentation. The Daily Digest example, state ledger, and test plan are editorial exercises, not a measured iOS run or a candidate-reported interview script. No employer-specific question set is claimed.
Which background strategy matches the work?
Apple's background-strategy guide distinguishes three needs that candidates often collapse. If the user starts a short, important operation in the foreground and then backgrounds the app, a UIKit background assertion via beginBackgroundTask(withName:expirationHandler:) may allow time to finish. If the app can defer a short content update until the system chooses a launch opportunity, use a scheduled BGAppRefreshTask. If it can defer more intensive maintenance, consider BGProcessingTask. Long file transfers have a different tool: a background-configured URLSession.
These choices differ by when the work originates and who owns the next launch. A UIKit background assertion extends current process runtime; it is not a request to start a suspended app every fifteen minutes. A BGAppRefreshTaskRequest asks the scheduler for a later opportunity; it does not continue the exact foreground stack frame. A background URL session lets the system manage a transfer across app suspension. Name the business requirement before naming an API.
For the editorial Daily Digest fixture, a reader opens an app at 08:00 and wants reasonably fresh headlines on their next visit. A short refresh is deferrable; a BGAppRefreshTask fits. A large local index rebuild can be a separate BGProcessingTask. Sending an already-composed message when the user immediately closes the app is a continuation case. Putting all three into one scheduled refresh handler makes the timing and cancellation contract impossible to explain.
What does submitting a request guarantee?
The earliestBeginDate reference is precise: it means the task should not start before that date. It does not guarantee a launch at that date. The system considers conditions such as device use and power. Therefore a statement like “schedule refresh for 08:15 and the digest will update at 08:15” is not supported. The next foreground opening must still handle stale data gracefully.
The app registers its task identifier and launch handler during startup, includes the identifier in BGTaskSchedulerPermittedIdentifiers, and submits a matching request. Apple's using-background-tasks guide shows registration, submission, an expiration handler, and setTaskCompleted(success:) in one lifecycle. Submission can fail; the app should record the error and retain a foreground fallback rather than treating a successful call to its scheduling helper as proof that work ran.
For Daily Digest, define “fresh enough” as a product requirement, perhaps “display the last cached issue immediately and refresh when the app opens if it is older than the accepted window.” That window is an editorial assumption, not an iOS service level. It lets the UI remain useful whether the system runs the background request promptly, much later, or not before the next foreground visit.
Walk through one refresh request
Use an illustrative state ledger. The app registers com.example.digest.refresh, submits a request whose earliest start is later, and then exits. At some system-chosen point, the app is launched and receives a BGAppRefreshTask. Its handler may arrange a future request, starts bounded fetch and persistence work, and installs an expiration handler. When work finishes, it calls setTaskCompleted(success:).
| State | Evidence to record | Required decision |
|---|---|---|
| Request submitted | Identifier, submission result, earliest date | Do not promise an exact launch time. |
| Handler entered | Task identifier and start timestamp | Start only bounded, restartable work. |
| Expiration signaled | Cancellation timestamp and active operation | Cancel or checkpoint; stop accepting new work. |
| Work quiescent | Saved version or recorded failure | Report completion once with an honest success value. |
This table is an editorial protocol, not a transcript from a device. Apple's sample resubmits from the launch handler; that is a pattern for future opportunities, not evidence that an individual request repeats forever. If the network response arrives just after expiration, a state machine must decide whether the result is still safe to persist. An interview answer should make that race visible rather than saying merely “call cancel.”
How do expiration and completion interact?
A task's expirationHandler is a warning that the system needs the background opportunity back. Apple's guide shows using it to cancel the work operation. Cancellation is a request, not instant proof that a callback has stopped. A fetch may still finish or a database write may still be in progress. The handler and work completion path must coordinate so the app does not commit partially valid state or call setTaskCompleted twice.
One conceptual implementation uses a single owner for the task result. It records whether the operation is active, whether expiration has been requested, and whether completion has already been reported. On success, it persists a complete digest version and reports success. On error or expiration, it stops or checkpoints the operation, then reports failure after cleanup. The exact Swift synchronization mechanism—an actor, serial queue, or guarded operation—is an implementation choice; the invariant is one terminal report per launched task.
Do not call setTaskCompleted(success: true) immediately after dispatching asynchronous work. The system would be told that the task is done while the work still depends on background runtime. Do not wait forever for a callback that ignores cancellation, either. Bound network and database operations, make interruption observable, and design the next run to resume safely from a saved checkpoint or repeat an idempotent fetch.
Why does idempotence matter even with one completion call?
Reporting completion once says something about one launch instance. It does not guarantee the business operation ran exactly once across launches. The process can terminate after writing a new digest but before recording the task outcome. A later refresh can fetch the same issue again. If the app blindly appends articles each time, duplicates appear even though every handler calls setTaskCompleted exactly once.
For the fixture, store the digest under a stable issue ID and replace or upsert that version atomically. Keep a last-successful-refresh marker only after the content commit succeeds. On a retry, the same issue ID should lead to the same visible state. This is editorial engineering advice derived from the interruption model, not a guarantee provided by Background Tasks. It is also why “the task completed” and “the content is current” require separate observations.
The same distinction applies to user-visible notifications. If a refresh sends a notification, save a deduplication key with the issue and decide whether an interrupted send can be retried. A background scheduler does not supply a universal exactly-once delivery contract for the product. The candidate should state the boundary of each guarantee rather than promising a user outcome from one API callback.
When is a UIKit background assertion the right answer?
Apple's UIKit background-execution guide addresses work already underway as the app moves out of the foreground. The app begins an assertion, performs its critical bounded work, and calls endBackgroundTask(_:) after finishing. Its expiration handler must be ready to stop or defer the work if time runs out. Failing to end the assertion can lead the system to terminate the app.
That mechanism fits a short save operation better than a nightly maintenance plan. It is not a license to run arbitrary computation in the background. If the app must upload a large media file, Apple's strategy guide directs developers toward a background URLSession. If the app can wait for favorable system conditions to rebuild an index, a scheduled processing task better states that intent.
An interview follow-up might ask what happens if the user force-quits the app or disables background refresh. Avoid a universal prediction across app state, OS version, and task type. State that scheduled execution is system-controlled, identify the documented constraints for the chosen mechanism, and preserve foreground recovery. The product should not depend on a background launch as its only path to correctness.
How would you test the lifecycle without pretending to control iOS?
Separate unit-testable state logic from scheduler integration. A fake clock and fake worker can test “expiration arrives before response,” “response arrives before expiration,” “commit succeeds but completion reporting is interrupted,” and “handler receives the same issue on a later run.” Assert a single terminal callback for the launch and no duplicated committed digest. These are deterministic tests of the app's protocol, not proof of OS scheduling behavior.
Then test registration, permitted identifiers, handler invocation, and device behavior using Apple's supported debugging workflow and a physical-device plan when required. The sample project explicitly notes that it must run on a physical device. Record OS version, background mode, identifier, submit error, actual launch timestamp, expiration, and completion. Do not infer “iOS never runs our task” from a short simulator wait.
For a production incident, inspect both sides of the contract. If the handler never runs, check registration and permissions, submission results, and system conditions. If it runs but data remains stale, inspect worker errors, cancellation, persistence, and whether completion was reported prematurely. If it runs repeatedly, inspect the issue key and checkpoint logic. The diagnosis changes with the first missing transition.
Fast interview answers
Does earliestBeginDate schedule an appointment? No. It sets a lower bound; the system chooses when, or whether within the observed window, to launch.
Can beginBackgroundTask replace BGAppRefreshTask? No. The former extends current work as the app backgrounds; the latter requests a future system launch for a short refresh.
What should expiration do? Signal cancellation or checkpointing, stop unsafe new work, and coordinate an honest terminal completion report.
Does setTaskCompleted(success: true) prove the UI is current? No. Verify the content commit and next foreground rendering separately.
Where does a large upload belong? Consider background URLSession rather than forcing a short refresh task to hold an unbounded transfer.