Depop: Backend-Driven UI on iOS

Below is an expanded version of the report, with a new section that explicitly addresses the risks of backend-driven UI and how I mitigated them. The problems are framed as engineering trade-offs, not weaknesses — reinforcing seniority and leadership.


About the project

The project introduced a backend-driven UI (BDUI) approach into Depop’s iOS application to improve delivery speed, reduce client-side complexity, and minimize user-facing inconsistencies.

Previously, significant UI logic and validation lived on the client. This increased coupling between releases, created fragmented behavior across platforms, and made quick fixes expensive. The goal was to shift UI composition and flow control closer to the backend, while keeping the iOS app focused on rendering, performance, and interaction quality.

The system was introduced incrementally, coexisting with legacy UI to reduce risk and ensure production stability.


Responsibilities

My role combined technical ownership with cross-functional coordination:

  • Designed the backend-driven UI engine for iOS
  • Defined backend ↔ client contracts and schema evolution rules
  • Implemented rendering, validation, and fallback mechanisms in Swift
  • Led incremental rollout and guarded adoption in production
  • Aligned backend, product, QA, and customer support expectations
  • Measured success beyond engineering metrics

Backend-driven UI engine

Architecture overview

The engine followed three core principles:

  1. Declarative UI schemas controlled by backend
  2. Strongly typed decoding and validation on iOS
  3. Thin, deterministic rendering layer

Backend JSON → Schema Decoder → UI Model → Renderer → Native Views

Example: backend schema

json
{
  "type": "form",
  "title": "Report an issue",
  "components": [
    {
      "type": "text",
      "id": "description",
      "required": true,
      "placeholder": "Describe the issue"
    },
    {
      "type": "button",
      "action": "submit",
      "title": "Send"
    }
  ]
}

Swift model layer

swift
enum ComponentType: String, Decodable {
  case text
  case button
}

struct UIComponent: Decodable {
  let type: ComponentType
  let id: String?
  let title: String?
  let required: Bool?
  let placeholder: String?
  let action: String?
}

Rendering layer

swift
func render(component: UIComponent) -> UIView {
  switch component.type {
  case .text:
    let textField = UITextField()
    textField.placeholder = component.placeholder
    return textField

  case .button:
    let button = UIButton(type: .system)
    button.setTitle(component.title, for: .normal)
    return button
  }
}

The renderer is intentionally minimal. Business rules, flow decisions, and edge cases are controlled via backend configuration rather than client branching.


Challenges of backend-driven UI — and how I addressed them

1. Loss of compile-time safety

Problem: backend-driven UI shifts part of the UI definition outside the compiler’s control. Invalid schemas, missing fields, or unexpected combinations can surface only at runtime.

Mitigation:

  • Strict decoding with explicit failure paths
  • Schema versioning with backward compatibility guarantees
  • Analytics on decoding errors to detect backend regressions early
  • Defensive defaults instead of silent failures

This preserved runtime safety without reintroducing client-side complexity.

2. Increased backend responsibility for UX

Problem: backend teams suddenly influence UX decisions traditionally owned by client teams, which can lead to degraded user experience if unmanaged.

Mitigation:

  • Clear ownership boundaries: backend controls structure, client controls presentation
  • Shared documentation with UX constraints encoded into schema contracts
  • Limited component vocabulary — no free-form UI primitives

This ensured flexibility without sacrificing design consistency.

3. Debugging complexity

Problem: failures are harder to trace when UI behavior depends on remote schemas rather than static code.

Mitigation:

  • Schema payload logging tied to user sessions
  • Feature-flagged schema rollout
  • Ability to force native fallback UI for affected screens
  • Support tooling to reproduce user-reported issues using payload snapshots

This reduced MTTR and improved collaboration with support teams.

4. Performance risks

Problem: dynamic UI composition can introduce layout and rendering overhead, especially on complex screens.

Mitigation:

  • Pre-decoding and caching schemas
  • Avoiding deep nesting and recursive layouts
  • Native view reuse instead of generic container trees
  • Performance budgets per screen enforced during rollout

As a result, performance remained on par with static UI.

5. Overuse and loss of simplicity

Problem: backend-driven UI can become a hammer used for every nail, leading to overly abstract systems that are hard to reason about.

Mitigation:

  • Explicit criteria for when BDUI is allowed
  • Static UI retained for performance-critical or interaction-heavy screens
  • Regular audits to prevent schema sprawl

BDUI was treated as a tool, not a default.


Achievements & impact

Operational impact

  • ~15% reduction in customer support workload
  • Many tickets originated from unclear flows, outdated copy, or inconsistent UI behavior. Backend control enabled fast fixes without app releases.

Engineering impact

  • Faster iteration without App Store delays
  • Fewer UI-related hotfixes
  • Clear separation of responsibilities between backend and client
  • Higher confidence in releases due to deterministic behavior

Product impact

  • Faster experimentation and A/B testing
  • Consistent behavior across platforms
  • Reduced dependency on mobile release cycles

Closing note

This project was not about “rendering JSON.” It was about moving complexity to the layer best equipped to handle change.

Backend-driven UI worked because it was introduced with discipline: clear boundaries, explicit constraints, and a strong bias toward operational safety. The result was not only a more flexible UI system, but a measurable improvement in product velocity and support efficiency.


Questions for next iteration

  • Should we explicitly add a “When not to use BDUI” subsection?
  • Do you want a short post-mortem-style reflection on trade-offs after long-term use?
  • Should this case be framed more toward staff / principal engineer positioning?

If you want, next step we can tighten this into a 1-page executive-friendly version for recruiters or founders.