---
title: 'The Case for Cross-Platform'
source: 'https://academia.sh/en/courses/cross-platform-approaches/cross-platform-rationale'
course: 'Cross-Platform Approaches'
language: en
updated: '2026-10-21T05:02:49+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Classifies the sharing of a feature's code between two platforms as shared, branched, split, or single"
    - 'Explains the cost of two separate codebases and the promise of a single codebase'
    - 'Shows why a function that returns a constant, whatever its input, always comes out shared'
    - 'Measures how the same function can move from shared to branched depending on the sample list'
---

# The Case for Cross-Platform

The case for cross-platform development is how many separate codes the same feature is written in on two platforms. The lesson classifies the sharing of a feature's code with four answers: shared, branched, split, single.

The previous two course series built an app twice from start to finish: once for one platform, once for the other. Read side by side, the two series show the same features written twice. The function that formats a price, the call that requests a permission, the response to the back button: each exists in both codebases. This course is about why that repetition exists, what it costs, and the ways proposed to reduce it.

The single question of the course starts in this lesson: **how is this feature's code shared between the platforms?** The answer takes four forms, and their names do not change for the rest of the course. If the feature runs the same code on both platforms and that code gives the same result on both platforms for every sample, the sharing is **shared**. If the same code runs but gives a different result on the platforms for at least one sample, the sharing is **branched**. If each platform has its own code, even when the results are identical, the sharing is **split**. If the feature's code is missing entirely on one of the platforms, the sharing is **single**. The umbrella name for the four answers is sharing; when someone speaks of a feature's sharing, one of these four is meant.

This question resembles the "what determines this result" question from M13/K04, but it is not the same. There the platform was an environment that determined the result, and what was measured was the source of the result. Here the source of the result is not asked about: what is asked is how many separate codes the same feature is written in, and on which platforms.

## The Rule of Sharing

A label does not give the sharing; a rule does. The core below takes a feature's implementation on the two platforms and a list of samples. If a platform has no implementation, the sharing is single. If the two implementations are separate function objects, it is split. If it is the same function and no sample makes the two platforms' results differ, it is shared; if some sample does, it is branched.

```javascript
// sharing.mjs — classifies how a feature's code is shared between the platforms
export const PLATFORMS = ["ios", "android"];

export class Sharing {
  constructor(label, impl, samples) {
    this.label = label;
    this.total = samples.length;
    this.missing = PLATFORMS.filter((p) => typeof impl[p] !== "function");
    const present = PLATFORMS.filter((p) => !this.missing.includes(p));
    this.code = new Set(present.map((p) => impl[p])).size;
    if (this.missing.length > 0) {
      this.differ = null;
      this.verdict = "single";
      return;
    }
    this.differ = samples.filter((s) => {
      const outs = PLATFORMS.map((p) => JSON.stringify(impl[p](s, p)));
      return new Set(outs).size > 1;
    }).length;
    if (this.code > 1) this.verdict = "split";
    else this.verdict = this.differ === 0 ? "shared" : "branched";
  }

  line() {
    const detail = this.differ === null
      ? `missing=${this.missing.join(",")}`
      : `diff=${this.differ}/${this.total}`;
    console.log(`${this.label}: code=${this.code} | ${detail} -> ${this.verdict}`);
  }
}

export function countSharing(items) {
  for (const name of ["shared", "branched", "split", "single"]) {
    console.log(`${name}: ${items.filter((s) => s.verdict === name).length}`);
  }
}
```

`PLATFORMS` is the platform list; the `Sharing` class takes a feature's label, its implementations, and its samples. The `code` property holds how many separate function objects exist, and the `differ` property holds in how many samples the result differs by platform. `countSharing` counts the four sharings in a fixed order and prints a zero as well. The rule is the only place that does this work: a sharing is never assigned by hand.

The rule does not see two things. The first is cost: a three-line split and a three-hundred-line split count the same. The second is the sample that is not given: the rule sees only what the given samples show. The lesson text, not the rule, speaks about cost and about the choice of samples.

The measurement in the lessons is built as follows. A feature's implementation on the two platforms is written as a table of functions: a function under the `ios` key, a function under the `android` key. The table does not run a real compiler or a real platform.

**AP1.** The model builds this sharing's rule; the model is not the cross-platform framework itself. On a platform, a feature's code lives in a file, a build target, or a module. In the model, the code itself is a function, and saying "the same code" on two platforms means pointing at the same function object in the table.

## Four Features, Four Sharings

Take four features of an app: the function that formats a price, the function that shortens a title, a permission request, and the back button. In the first, both platforms run the same function and the function ignores the platform. In the second, the same function runs but the shortening limit changes with the platform. In the third, each platform has its own permission function; even if both produce the same text, two separate functions were written. In the fourth, the back button exists only on Android, and there is no function on the iOS side. Each feature is measured with four samples.

```javascript
// main.mjs
import { PLATFORMS, Sharing, countSharing } from "./sharing.mjs";

function formatPrice(cents) {
  return `${Math.floor(cents / 100)}.${String(cents % 100).padStart(2, "0")}`;
}

function shortTitle(title, platform) {
  const limit = platform === "ios" ? 12 : 16;
  return title.length > limit ? title.slice(0, limit) + "..." : title;
}

function iosPermission(kind) {
  return `permission:${kind}`;
}

function androidPermission(kind) {
  return `permission:${kind}`;
}

function handleBack(screen) {
  return screen === "home" ? "exit" : "back";
}

const prices = [0, 199, 1250, 99999];
const titles = ["Coffee", "Weekly budget", "Shopping list", "Annual subscription renewal"];
const kinds = ["notification", "camera", "location", "contacts"];
const screens = ["home", "cart", "checkout", "profile"];

const features = [
  new Sharing("price-format", { ios: formatPrice, android: formatPrice }, prices),
  new Sharing("title-shortening", { ios: shortTitle, android: shortTitle }, titles),
  new Sharing("permission-request", { ios: iosPermission, android: androidPermission }, kinds),
  new Sharing("back-button", { android: handleBack }, screens),
];
console.log(`platforms: ${PLATFORMS.join(", ")}`);
features.forEach((f) => f.line());
countSharing(features);
```

```text
platforms: ios, android
price-format: code=1 | diff=0/4 -> shared
title-shortening: code=1 | diff=3/4 -> branched
permission-request: code=2 | diff=0/4 -> split
back-button: code=1 | missing=ios -> single
shared: 1
branched: 1
split: 1
single: 1
```

The four features gave four different sharings: shared 1, branched 1, split 1, single 1. This distribution is the binding number of the course; the sample count is 4 and the platform count is 2. Later lessons do not print this number again; they refer to it.

`price-format` is shared. `code=1` says the two platforms run the same function, and `diff=0/4` says the four prices are written the same on both. `title-shortening` is branched: the code is still one, but `shortTitle` chooses the limit by platform. "Weekly budget" is thirteen characters: on iOS it is cut after the twelfth character, on Android it is not cut. "Shopping list" is also thirteen characters and is cut the same way, on iOS only. "Annual subscription renewal" is twenty-seven characters; it is cut on both platforms, but at different places. Of the four titles, three differ in the result. `permission-request` is split: `code=2` means two separate functions, and `diff=0/4` shows that for now the two say the same thing. `back-button` is single: `missing=ios` says there is no iOS implementation, and `diff` cannot be computed.

## Where the Rationale Comes From

The cross-platform decision usually comes from three observations. The first is team size: two codebases mean two skill sets and often two teams. The second is product consistency: writing the same rule separately on two platforms carries the risk that the rule gives different results on the two platforms. The third is release rhythm: a feature can ship on one platform and wait weeks for the other. All three are real reasons, and all three point to the same place in the measurement: reducing the number of split and single features.

The other side of the rationale can also be read from the same measurement. A branched feature carries the difference between the two platforms inside it even when the code is one; whoever writes that difference has to know both platforms. A single feature does not become shared even if the codebase is merged, because the capability the shared code would run on may not exist on one platform. For this reason the cross-platform decision is not made as "reduce two codes to one" but with the question, for each feature, "which sharing does it fall into". The following lessons ask this question for each of the four approaches and tie it to approach selection in the last lesson.

## The Cost of Two Codebases

The typical picture of a split sharing is this: the same rule is written twice, in two languages. The two pieces below are the split form of a price formatter. The result is the same; the code was written twice.

```swift
// Platform code: PriceFormatter
import Foundation

func formatPrice(cents: Int) -> String {
    String(format: "%d.%02d", cents / 100, cents % 100)
}
```

```kotlin
// Platform code: PriceFormatter
fun formatPrice(cents: Int): String =
    "%d.%02d".format(cents / 100, cents % 100)
```

These two pieces are not measured; the model builds this sharing's rule and does not run a platform compiler. They are still enough to show the cost. Someone who changes the rule touches two files, tests the two files with two separate test setups, and ships them in two separate releases. The two codes give the same result at first; when one is forgotten, they drift apart. A drifted split is a defect that becomes harder to find as the team grows.

The promise of a single codebase is to pay this cost once. The rule is written in one place, tested in one place, and goes to both platforms together. The limit of the promise is also visible in the measurement: even when the code stays single, a feature like `title-shortening` branches by platform, and the branch needs maintenance too. Increasing sharing does not make a feature behave the same for every sample.

## The Same Cart, Two Platforms

The cart total is a good example of a shared rule. Each line in the cart carries a unit price and a quantity, the total is found in cents, and it is formatted when written to the screen. The function never reads the platform. In the same block we also add a constant that does not depend on its input: the shop name. A function that returns a constant is legitimate, but it always gives shared, because no input can change the result. This is not a guarantee but a natural consequence of the rule.

```javascript
// main.mjs
import { Sharing, countSharing } from "./sharing.mjs";

function cartTotal(lines) {
  return lines.reduce((sum, [unitCents, quantity]) => sum + unitCents * quantity, 0);
}

function shopLabel() {
  return "Corner Ledger";
}

const carts = [
  [[199, 2], [1250, 1]],
  [[99, 3]],
  [[0, 5], [500, 1]],
  [[99999, 1], [1, 1]],
];

const features = [
  new Sharing("cart-total", { ios: cartTotal, android: cartTotal }, carts),
  new Sharing("constant-label", { ios: shopLabel, android: shopLabel }, carts),
];
features.forEach((f) => f.line());
countSharing(features);
```

```text
cart-total: code=1 | diff=0/4 -> shared
constant-label: code=1 | diff=0/4 -> shared
shared: 2
branched: 0
split: 0
single: 0
```

Both features came out shared, and `diff` is zero for both. `cart-total` is shared because the function looks only at its own input. `constant-label` is shared because its result depends on no input. The two are not shared for the same reason: in the first, the function really processed the input and gave the same result on both platforms; in the second, there was nothing to process. This difference is not visible in the rule, because the rule looks only at the `diff` value. The reader's job is to ask, when a feature comes out shared, "what did this feature do".

**AP2.** The unit prices in the cart lines are integers in cents; the model does not know decimal and currency rules. On a platform these rules come through locale and formatter classes; in the model, the total is only an integer sum.

## The Bounding Measurement

Now the same function is measured twice. Think of the badge text of an app icon: if the unread count exceeds a limit that differs by platform, it turns into "99+" or "999+". The badge function reads the platform and carries a branch. It is first measured with three samples that never touch the branch: zero, five, and forty-two unread messages. Then the same function is measured with four samples: one hundred fifty is added to the list.

```javascript
// main.mjs
import { Sharing, countSharing } from "./sharing.mjs";

function badgeText(count, platform) {
  const limit = platform === "ios" ? 99 : 999;
  return count > limit ? `${limit}+` : String(count);
}

const quiet = [0, 5, 42];
const busy = [0, 5, 42, 150];

const features = [
  new Sharing("badge-3-samples", { ios: badgeText, android: badgeText }, quiet),
  new Sharing("badge-4-samples", { ios: badgeText, android: badgeText }, busy),
];
features.forEach((f) => f.line());
countSharing(features);
```

```text
badge-3-samples: code=1 | diff=0/3 -> shared
badge-4-samples: code=1 | diff=1/4 -> branched
shared: 1
branched: 1
split: 0
single: 0
```

The function did not change; `badgeText` is the same function in both measurements, and `code=1` stays. Only the sample list changed. The first three samples stayed below the branch's threshold, so the two platforms gave the same text and the sharing came out shared. The sample of one hundred fifty exceeds the iOS threshold and does not exceed the Android threshold; iOS produces "99+" while Android produces "150", and the sharing turned branched.

This result has two readings, and both are correct. The first is that the rule sees only what the given samples show: a branch looks shared until a sample touches it. Before saying a feature is shared, it is necessary to make sure the sample that triggers the branch is in the list. The second is the limit of the cross-platform rationale: keeping the code single does not remove the branch. The branch has moved inside the code; it has not left the rule.

**AP3.** The two thresholds, 99 and 999, are inputs of the model. In a real app, the badge limit is a platform and product decision; in the lesson these numbers are not presented as a platform rule.

## Summary

- This lesson's sharing: shared. The lesson introduces the four sharings, and the cart total runs on both platforms with the same function, without reading the platform.
- In the core measurement, four features gave four sharings: shared 1, branched 1, split 1, single 1; the sample count is 4 and the platform count is 2.
- A split is the same rule written twice; even when the results are the same, maintenance is spread over two files and two test setups.
- A function that returns a constant always comes out shared, because no input changes the result.
- The same badge function came out shared with three samples and branched once the fourth sample was added: the rule sees only what the given samples show, and it does not see what it costs.

## Next Step

This lesson established the codebase count and the concept of sharing. Cross-platform approaches change this concept with different mechanisms. The next lesson, `The WebView-Based Approach`, measures the mechanism that draws the interface from a single page, and how the input the page takes from the platform shell changes the sharing.
