Mobile architecture
ADR examples for mobile teams, with a template
Most ADR templates were written for servers, where a bad decision can be redeployed away. Mobile decisions live on in installed builds. A copyable ADR template with a versions-in-the-wild section, three complete mobile examples, revisit triggers that name evidence, where the records live, and how they keep a decision closed.
14 min read
Search for an architecture decision record template and you will find good ones. Context, decision, consequences, status. Almost all of them were written for systems that can be redeployed. If the decision was wrong, you ship the old version again by lunch. A mobile team cannot do that. The build that carried the decision is installed on phones you do not control, and it keeps calling your API until its owner updates.
I have run 500+ technical interviews from the hiring side over 15 years in mobile, and "tell me about an architecture decision you would make differently" is one of the prompts where the record shows. The candidates who kept one can say what they rejected, what they accepted on purpose and what would have changed their mind. The rest describe a diagram.
This page is the record itself: a template you can copy, three complete mobile ADRs, and the habits that keep them useful. If you are writing the larger proposal that comes before the decision, the technical design doc template is the companion page; it also covers when an ADR alone is enough.
- An ADR records a decision after it is made: options, consequences accepted, and the evidence that would reopen it.
- On mobile, add one section generic templates skip: versions in the wild.
- Revisit triggers name evidence, not dates. A new record supersedes the old one; nobody edits history.
- Keep the records next to the code, numbered, indexed and linked from the pull request.
What an ADR is for
My book Controlled Change opens its chapter on decisions with the line that should sit at the top of every ADR folder: "An Architecture Decision Record is useful when it preserves reasoning. It is useless when it merely announces an outcome."
An ADR exists for the engineer who arrives in eighteen months, finds an odd boundary or a queue that looks overbuilt, and wants to delete it. Without the record, they either repeat the old debate or assume the people before them were careless. With it, they can see the pressure that was real at the time, the obvious option that was rejected and why, and the condition under which the answer was supposed to change. The book puts the standard plainly: "The best ADR is not the longest. It lets a future engineer reconstruct the decision without reconstructing the crisis."
That is also why an ADR is not a proposal. The proposal argues; the record remembers. A proposal can run to many pages and invite objections. An ADR is short, written at or right after the decision, and outlives everything that came before it.
When a mobile decision deserves an ADR
Not every choice does. Controlled Change sorts decisions by reversibility and blast radius: a local, reversible choice such as a view's state shape gets code review and no ADR; a cross-feature contract such as navigation gets a short ADR with an owner; persisted schemas, shared platform policy and external commitments need more than a record. Before the Room draws the bottom line from the other side: no ADR "for reversible decisions that cost less to redo than to record."
On mobile, the decisions that clear the bar tend to be the same ones every year:
- a persisted schema, an operation format or anything queued on the device;
- a sync, offline or conflict policy;
- a minimum supported app or OS version, or a forced-update rule;
- a module boundary other teams will build against;
- a navigation or deep link contract;
- a third-party SDK that sees user data, or a new data flow to a vendor;
- a state or concurrency pattern adopted across features;
- a decision imposed from above that the team has to make safe.
The last one surprises people. Controlled Change's final case is a framework, a date and two vendors fixed by a contract before any engineer was asked, and its answer is to write the record anyway: "An imposed decision still deserves an ADR". Nobody else will write down the assessment, the conditions and the triggers that would reopen it.
The template, ready to copy
Copy the block below into a new file in your repository. The header is short on purpose; the section most templates do not have is the fourth one.
# ADR-NNN: <the decision, as what we now do>
Status: proposed | accepted | superseded by ADR-NNN | rejected | retired
Date:
Decision owner: <one name: closes or revises this record>
Consulted: <owners of each boundary this touches>
Supersedes: <ADR-NNN, or none>
Scope: <where this applies, and where it does not>
## Context
The pressure that forces a decision now, with dates and evidence.
## Options considered
A. Keep the current design, and what that costs.
B.
C.
Describe each so the people who prefer it would sign it.
## Decision
The chosen option, in operational language.
## Consequences accepted
What we gain, what we pay, what we defer. On purpose.
## Versions in the wild
Oldest supported app version, and what it does after this ships.
Persisted data and queued work written by older builds.
API window: which client versions call which shape, until when.
Rollback: possible once new data is written? If not, the brake.
## Rollout and deletion
Stages, flag, halt metric. What gets deleted, on what evidence.
## Revisit triggers
Evidence, not dates. Each with a data source and an owner.
## Exceptions
Owner, reason, expiry, way back. Anything else is divergence.
## Outcome notes
Added after rollout: what the evidence showed.A few fields earn a sentence. The decision owner is one name, not a team: Before the Room's rule is that the person who decides is the one whose yes holds and whose no is final, and that the person who can reopen a decision is the one most often left off the page. Options start with keeping the current design, because the cost of doing nothing is the comparison every other option needs. Consequences are things you accept on purpose; if a reader would be surprised by one in six months, it belongs here. And outcome notes are added after rollout, which turns the record from a prediction into evidence.
The section generic templates miss: versions in the wild
Controlled Change has a short sentence about why mobile is different: a decision "that requires all clients to update is usually not deployable." A server ADR can say "roll back if errors rise". A mobile ADR has to say what rollback means when the build that made the change is already on phones.
The section asks four questions, in the order a reviewer should ask them:
- What does the oldest supported build do after this ships? Not the build you are about to release; the oldest one still calling your API.
- What happens to data and queued work written by older builds? A pending operation created by last year's app may be sent by this year's app to next year's server.
- Which client versions call which API shape, until when? The window, its owner and the traffic level that closes it.
- Can you roll back once new data has been written? If not, name the brake that works instead: a server-side flag, a config value, a server refusal.
Sometimes the honest answer is "none of this changes", and that is still worth writing down. The module split below has almost nothing in the section, and the one line it does have is the one that would have broken old saved carts.
Example 1: adopting a sync engine for offline edits
The three records below are illustrative. The app, the teams, the version numbers and the rates are invented to show the shape of a good record, not taken from a real project. Each one links to a longer article on the same decision; the ADR is what that decision looks like once it is made.
The first is a field-service app whose technicians lose notes when the app is killed underground. The interesting part is the scope line: the record adopts a sync mechanism for two kinds of data and refuses it for the one the server must own. That split, and the build-or-adopt reasoning behind option B, is the subject of how to choose a sync solution for a mobile app.
# ADR-014: Edits to job notes and photos are saved to a local
# operation ledger and synced; job status stays online-only
Status: accepted
Date: <date>
Decision owner: Mobile lead, Jobs
Consulted: Jobs API lead, support lead, privacy reviewer
Supersedes: none
Scope: notes and photo attachments on assigned jobs.
Not job status, assignment or signatures.
## Context
Technicians work in basements and plant rooms with no signal.
Notes typed there are held in memory and lost if the app is
killed. Support logs repeated "my notes disappeared" tickets,
and technicians retype from paper at the end of the day.
## Options considered
A. Keep online-only, and say "needs a connection" plainly.
Cheapest. Does not fix lost work, which is the complaint.
B. Adopt a hosted sync product for the whole job model.
Fast start. Puts conflict rules for job status, which the
server must own, inside a client-side merge we cannot see,
and makes our identifiers and data its format.
C. Build a small operation ledger for notes and photos only.
Each edit becomes a durable operation with an idempotency
key; the server stays the authority and returns a receipt.
## Decision
Option C. Notes and photos are written to the ledger before the
UI says "Saved on this device". A worker sends them when a
connection exists; the UI says "Sent" only after a receipt.
Job status changes keep requiring a connection.
## Consequences accepted
A local schema, its migrations and fixtures from every
supported version. Server-side idempotency for two endpoints.
New states for support to explain: saved, waiting, sent,
needs attention. No offline status changes, on purpose.
## Versions in the wild
Builds before 5.0 have no ledger and write directly. The API
accepts direct writes and ledger operations side by side until
pre-5.0 traffic is below the support floor.
Queued operations carry a payload version. Every later build
keeps a decoder for each version still pending on devices.
A pending queue must survive an app upgrade and a logout.
Rollback: a 5.0 build cannot be recalled. The brake is a
remote flag that sends new edits down the online path; items
already in the ledger still drain.
## Rollout and deletion
Internal devices, then 5% of technicians, then all. Halt if
the oldest pending operation exceeds its agreed age, or if
duplicate notes appear on the server. Delete the direct-write
path when pre-5.0 traffic reaches zero for two releases.
## Revisit triggers
Conflicting edits to the same note exceed the agreed rate for
four weeks. Product asks for offline status changes. A second
domain needs offline writes: reopen build versus adopt for the
shared mechanism, not for conflict rules.
## Exceptions
None open.
## Outcome notes
<added after rollout>Read the versions-in-the-wild section twice. It is where the record says the queue format is now a long-lived contract, which is the sentence the next engineer to touch the ledger most needs.
Example 2: raising the minimum supported version
The second retires an old API by raising the minimum supported app version. Controlled Change treats a hard minimum version as sometimes necessary and always expensive, and asks for a graduated policy: a soft recommendation, degraded capability with an explanation, a deadline, and a block only for narrowly justified risk, with the server still refusing unsafe operations whatever the client shows. The record below follows that ladder.
# ADR-021: Raise the minimum supported app version to 6.0
# and retire Orders API v1
Status: accepted
Date: <date>
Decision owner: Head of mobile, with the product director
Consulted: Orders API lead, support lead, legal
Supersedes: ADR-009 (Orders API v1 kept alive by translator)
Scope: iOS and Android consumer apps.
## Context
Orders API v1 cannot carry the new itemized tax fields. A
translator keeps it alive, and every order change is now built
twice. Builds before 6.0 are the only callers of v1. Their
share of active sessions has stopped shrinking, at a level the
support policy calls the floor.
## Options considered
A. Keep v1 and the translator. Costs a second implementation
of every order change, for as long as the tail lives.
B. Hard block pre-6.0 builds next week. Fastest; strands users
who have not seen any warning.
C. Soft prompt for six weeks, then a hard floor at 6.0, with
the server refusing only order creation on v1 meanwhile.
## Decision
Option C, with the floor date announced in the prompt.
## Consequences accepted
Some users on old devices that cannot run 6.0 lose ordering.
Support needs a reply for them. The translator is deleted.
## Versions in the wild
A version gate works only in builds that already ship it.
Builds 4.0 to 5.9 read the minimum version from remote config
and show the update screen. Builds before 4.0 have no gate:
on v1 refusal they show their generic error. Server returns
a message in the error body those builds already display.
On Android the 6.0 gate uses Play's in-app update flow; on iOS
the app shows its own prompt and opens the App Store page.
Neither store forces the update on its own.
Rollback: the floor is a config value. Lowering it restores
access, but only while v1 still runs on the server.
## Rollout and deletion
Prompt on day 1. Floor on day 43. Delete v1 and the translator
after two releases with no v1 traffic.
## Revisit triggers
Pre-6.0 share stays above the floor at day 43: owner takes the
date back to the product director as a decision. A security
or legal finding on v1: move the floor earlier.
## Exceptions
Enterprise customers on managed devices: owner, account lead;
expiry, day 90; path, managed rollout of 6.x.
## Outcome notes
<added after rollout>The line that saves this one is "A version gate works only in builds that already ship it." A floor set in remote config means nothing to a build that never reads it. Those users see whatever their build does with an error, which is why the server's error body matters more than the new update screen. How long to keep the window open in the first place is a separate decision with its own policy; how long to support old mobile app versions covers the adoption curve and the two floors.
Example 3: splitting a feature module
The third is a pure code reorganization, the kind of decision teams often leave unrecorded. It deserves a short ADR because other teams will build against the boundary, and because the reason for the split is exactly what a future engineer will need when deciding whether to merge it back.
# ADR-033: Checkout moves into its own feature module with
# one public entry point
Status: accepted
Date: <date>
Decision owner: Staff engineer, Checkout
Consulted: Mobile platform lead, Discovery team lead
Supersedes: ADR-002 (one app target) in part
Scope: Checkout code on iOS and Android.
Not payment SDK wrappers, which stay in platform.
## Context
Two teams now edit Checkout. Most of their merge conflicts sit
in shared app-target files, and a one-line Checkout change
rebuilds and retests the whole app.
## Options considered
A. Keep folders, add visibility conventions and code owners.
Cheap. Does not change build scope; conventions erode.
B. One module per layer (api, impl, ui, data) for Checkout.
Strict. Most Checkout changes would touch three of them.
C. One Checkout module (a Swift package target on iOS, a Gradle
module on Android) exposing a single entry point and route.
## Decision
Option C. Other features reach Checkout only through its entry
point and route. CI fails a build that imports Checkout
internals.
## Consequences accepted
A public surface to design and keep small. Graph rules to
maintain. Some shared helpers duplicated rather than shared.
## Versions in the wild
No change to the API, deep links or analytics event names.
Persisted data that encodes type names must still decode after
the move: test old saved carts and restored screens against a
fixture from the oldest supported build.
Rollback: revert before release; no runtime flag needed.
## Rollout and deletion
One release with the module in place and old paths removed.
Delete the old Checkout folders in the same change.
## Revisit triggers
Checkout and another module change together in most pull
requests for a quarter: merge them back. Checkout edit-to-test
time has not improved after six weeks: review the split.
## Exceptions
None open.
## Outcome notes
<added after rollout>Option B is there because it is the obvious choice to someone who has read about large-app architectures, and the record says in one line why it was rejected here. The revisit triggers are merge-back conditions, which is the part most module decisions forget. The four-question boundary test behind the decision, and the iOS and Android mechanics of enforcing it, are in when to modularize a mobile app.
Revisit triggers: evidence, not dates
The weakest line in most ADRs is "revisit in six months". Controlled Change is blunt about it: "Time alone is a weak trigger." A useful trigger observes one of the assumptions the decision rests on, so it fires when the world changes and stays quiet when it does not. The book's examples include build p75 above its agreed budget for four weeks, three teams needing the same domain capability, unsupported client share falling below a threshold, and an incident showing that the current authority policy is ambiguous.
Before the Room calls the same field reopen criteria and names how it fails: criteria so vague that any new opinion qualifies. Its correction is four words: "Name evidence, not discomfort." It also names the opposite failure. Set the bar so high that real evidence never clears it, and the record stops protecting the decision and starts freezing a mistake.
Three checks make a trigger usable. It names a measure someone already collects, or one the rollout adds. It has an owner who will notice when it fires. And it says what happens when it fires: reopen, review, or take it to a named person as a decision.
When a trigger does fire, write a new record. In the book's words, "A new ADR should supersede the old one rather than rewrite history." Mark the old record superseded, link both ways, and leave its reasoning intact. The chain of records is how the next engineer learns why the system looks the way it does.
Where ADRs live
Controlled Change's ADR review rules start with location: "Keep records close to the code or capability they govern." In practice that means:
- In the repository, as plain files. A folder per repo, or per module when modules have different owners. Records in a wiki drift away from the code they describe.
- Numbered and indexed. One index file with number, title, status and owner. The status is what tells a reader whether a record still governs anything.
- Linked from the pull request. The change that implements a decision links its ADR, and the ADR links the change. A pull request template field does this for free.
- Linked in both directions when superseded. A reader should be able to start at either end of the chain.
- Retired when the boundary disappears. A record for a module that no longer exists is marked retired, not deleted.
The book also keeps three artifacts apart that teams tend to merge: the ADR answers why this choice, a decision journal tracks uncertainty that is still open, and an architecture map shows the current boundaries. Folding them into one large document, it argues, makes all three harder to maintain. If your team has none of them, its advice is to start with the next consequential decision, not a campaign to document the past: an index, a compact template and statuses.
A decision log for the decisions below the bar
Many decisions are too small for an ADR and still worth a line: a library pinned to a version, a lint rule turned off for a reason, a temporary exception. A decision log holds them. Keep it to eight columns so people actually fill it in:
Date | Decision | Owner | Why (one line) | Rejected | Reversible? | Review on | ADRThe last column is the useful one. When a log entry turns out to be expensive to reverse, it graduates into an ADR, and the log points to it. A log nobody reviews becomes what Controlled Change calls a repository of decisions "that are never revisited", which it says "becomes historical fiction." Give the review date a real owner.
How an ADR keeps a decision closed
A record does not keep a decision closed by existing. Decisions rarely lose in a dramatic reversal. They lose to a pull request that implements the rejected option "just for now", an exception agreed in a chat thread the owner never saw, and an old path kept past its window. Before the Room's chapter on decision systems makes that the point: a decision holds when its reopen trigger is named, and divergence is defined in advance as behavior someone can point at.
Four habits do the work:
- Write the divergence list into the record. Implementing a rejected option without reopening the decision; an exception outside the exception path; a change agreed in chat that the owner never saw; an old path kept past its expiry. Disagreeing is not on the list.
- Route exceptions through one rule. Owner, reason, expiry and a way back. An exception without an expiry is a changed decision nobody recorded.
- Answer drift with a link, not an argument. When someone proposes the rejected option in a thread, the reply is the ADR and one question: does this meet the reopen criteria?
- Check the code against the record. A month after rollout, compare what the app actually does with what the ADR says it does, and add an outcome note.
When a respected engineer keeps working around a closed decision, Before the Room gives the sentence that separates new evidence from repeated disagreement without making it personal: "We have a closed decision here. If you have evidence that meets the reopen criteria, put it on the record and we take it to the owner. If not, the implementation follows the decision." It keeps the door open for the person who might be right and closes the side door. The longer version of what each side owes after the call is in disagree and commit for engineers.
When an ADR is not enough
An ADR records a decision; it does not make one. If the choice crosses teams, touches data authority or old clients, or commits you to something hard to undo, the record should be the last step of a proposal that was reviewed first. That proposal is the subject of the technical design doc template, which ends by producing exactly the kind of record on this page; for a shorter field checklist with review questions, see the mobile architecture RFC template.
The failure to watch for runs the other way too. Controlled Change lists what makes an ADR weak: written after the decision to create legitimacy, no rejected alternatives, vague consequences, no owner, never retired. It also names "architecture by precedent": an old ADR for a large feature copied into every new feature without checking its assumptions. The three examples above are shapes to copy, not decisions to copy.
How an ADR reads in a staff interview
From the hiring side, candidates rarely get asked about ADRs by name. They get asked about a decision: one they drove, one they reversed, one they disagreed with. The answers that read as staff carry the fields of the template without being prompted: the option they rejected and why, the cost they accepted on purpose, what the oldest build did after the change, and the trigger that would have reopened it. The answers that read as senior describe the chosen design well and stop.
If you are on the reviewing side of the table, how to review a mobile architecture proposal is where the record's questions come from: the constraint doing the work, the standard rising with irreversibility, and the decision written down at the end.
The short version fits on a card. Write the record when the decision is expensive to reverse. Name one owner. Keep the rejected options. Say what old builds do. Name evidence that would reopen it. Supersede, never rewrite. And keep it next to the code.
Questions engineers ask about ADRs
What is an architecture decision record?
A short document that records one architecture decision after it is made: the context that forced it, the options considered, the option chosen, the consequences accepted on purpose, and the facts that would reopen it. It lives next to the code it governs and is superseded by a new record, not edited, when the decision changes.
What should a mobile ADR contain that a generic template misses?
A section on versions in the wild. A server can be redeployed; installed app builds stay on devices until their owners update. A mobile ADR should say what the oldest supported build does after the change ships, what happens to data and queued work written by older builds, which client versions call which API shape, and whether rollback still works once new data has been written.
When should a mobile team write an ADR?
When a decision is expensive to reverse or crosses a boundary: a persisted schema, a sync or conflict policy, a minimum supported version, a module boundary other teams build against, a navigation contract, a third-party SDK that sees user data. For a local, reversible choice that costs less to redo than to record, code review is enough.
Where should ADRs be stored?
In the repository, close to the code or capability they govern, numbered, with an index and a status on each record. Link the ADR from the pull request that implements it, and link superseding records in both directions so the chain can be followed from either end.
What is the difference between an ADR and a decision log?
An ADR explains one consequential choice in enough depth that a future engineer can reconstruct it. A decision log is a running list of smaller decisions, one line each, with an owner and a review date. When a log entry turns out to be expensive to reverse, it graduates into an ADR.
