System Design (High-Level Design) Module 23 – Case studies: social, feeds and search
Design dating matches like Tinder
Design the matches of a dating app: decks of nearby people that suit both sides, swipes at peak load, one match per pair, private distances, blocks.
What you will learn
- Estimate the swipes, matches, deck builds and storage of a dating app from its daily users
- Design deck building from a cell index, with every filter applied in both directions
- Create exactly one match when two likes arrive at the same moment
- Protect people's locations and make blocks take effect at once
Before you start
What you’ll learn · Before you start
What you will learn
- Estimate the swipes, matches, deck builds and storage of a dating app from its daily users
- Design deck building from a cell index, with every filter applied in both directions
- Create exactly one match when two likes arrive at the same moment
- Protect people's locations and make blocks take effect at once
Before you start
On this page
At 20 million people a day, a dating app records 23,148 swipes a second and builds 463 decks of nearby people a second. Its matching system does three jobs: it shows each person a deck of nearby profiles that suit both people’s settings, it records every swipe, and when two people have liked each other it creates a match that opens a chat. Decks are built ahead of time from a location index, with every setting checked in both directions, so each swipe is a small write.
The hard parts are correctness and safety: two likes that arrive at the same moment must create exactly one match, the distance a card shows must never give away where someone is, and a block must work at once. This case study sizes the app and follows each of its paths.
Not affiliated
Based on public information about Tinder. MySmartCoPilot is not affiliated with Tinder, and Tinder’s real architecture may differ from what this lesson describes.
Both people’s settings must allow the pair
- Who may see whom? Each person sets the genders they want to see, an age range and a maximum distance of 2 to 100 km. A profile appears only when both people’s settings allow the pair.
- What makes a match? Two likes, in either order. A pass removes the profile from that person’s decks.
- After a match: both people hear about it within seconds and can chat; an unmatch ends the chat for both.
- Safety: blocks and reports, and distances shown only roughly.
- Out of scope: the chat itself (see chat delivery), payments and photo moderation.
Functionally, people report a position, fetch cards, like or pass, see their matches, and unmatch, block or report. The next card must be on the phone before the current one is swiped, so cards are fetched ahead and a page of them loads within 200 ms at the 99th percentile. An acknowledged swipe is never lost. A pair has at most one match, created within a second of the second like. An exact position never leaves the server, and a block applies on the next request.
Swipes are the biggest number; deck builds are the heaviest work
| Quantity | Assumption | Result |
|---|---|---|
| Swipes | 20 million people a day × 100 swipes | 2 billion a day: 23,148 a second, about 70,000 at an evening peak of 3 × |
| Likes | 30 % of swipes | 600 million a day, 6,944 a second |
| Matches | 3 % of likes find the other person’s like | 18 million a day, 208 a second, two notifications each |
| Swipe history | 32 bytes a swipe: two ids, the direction, the time | 64 GB a day, about 23 TB a year |
| Deck builds | 2 decks of 100 cards a person a day | 40 million a day: 463 a second, about 1,400 at peak |
| Deck cache | 20 million decks × 100 ids × 8 bytes | 16 GB |
| Photos | one 100 KB photo for each card shown | 200 TB a day from a CDN, about 18.5 Gbit/s |
Each swipe is a few dozen bytes. The heavy work hides in the deck builds: the example below reads 18,499 people to fill one deck, so 463 builds a second check about 8.6 million candidates a second.
Checkpoint
Check the scope and the estimate
Six records, from a person’s square to the pair of two likes
PUT /v1/me/locationtakes a latitude, a longitude and their accuracy; the server keeps only the person’s 1 km square and never returns it.GET /v1/deck?limit=25returns cards: profile id, first name, age, photos and a label such as “4 km away”.POST /v1/swipestakes the profile id, like or pass, and a swipe id made by the app, so a retry is stored once; it answers whether the swipe made a match.GET /v1/matches,DELETE /v1/matches/{id},POST /v1/blocksandPOST /v1/reportscomplete the API.
| Record | Key | Holds |
|---|---|---|
| Profile | person id | age, gender, settings, photos, last active |
| Cell index | cell id | the people active in the cell in the last 3 days |
| Pair | both ids, the smaller first | which of the two has liked the other, and the match once both have |
| Swipe history | swiper id, profile id | like or pass, and when |
| Deck | person id | the next 100 profile ids and when they were built |
| Blocks | person id | whom they blocked and who blocked them |
Decks are built ahead, so a swipe is one small write
The first complete design: positions, swipes, matches and decks
Text description of the diagram
The diagram shows the parts of the design from top to bottom.
- The mobile app calls the API gateway, which sends positions to the location service, requests for the next 25 cards to the deck service, and likes and passes to the swipe service.
- The location service keeps only the centre of the person's 1 km square and files the person under a 4 km cell of the cell index.
- The swipe service writes every swipe to the swipe history, through the swipe log. A like is also written, in one transaction, to the pair store, which holds both likes of a pair in one record. When that transaction finds both likes, it creates the match once, and the match goes to the notification and chat services.
- Ahead of need, the deck builder reads rings of cells from the cell index and past swipes from the swipe history, applies every filter in both directions, ranks the candidates and writes the next 100 profile ids to the deck cache.
- The deck service reads the next cards from the deck cache, checks blocks again and turns distances into labels.
Position. The location service keeps only the centre of the person’s 1 km square and files them under a 4 km cell of the cell index. Deck build. Ahead of need, a deck builder reads the person’s cell and enough rings of cells around it to cover their maximum distance, applies every filter in both directions, drops past swipes and blocks, ranks the rest and writes the next 100 ids to the deck cache. Serve. The app fetches 25 cards at a time; the deck service fills them in from profiles, checks blocks again and turns distances into labels. Swipe. Every swipe goes to the swipe log, which feeds the swipe history, kept by the person who swiped. A like also updates the pair record of the two people, on the shard: One of several databases that together hold a group's data, each with its own share of the rows, such as the accounts of one set of customers and everything that belongs to them. More in “First-hand case: how MySmartCoPilot is built” the pair’s key picks, in one transaction. Match. If that transaction finds the other like already there, it creates the match, the reply says “It’s a match”, and the other person gets a notification. Follow one like through those steps:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- A swipe right. Meera likes Arjun's card. The next cards are already on her phone, so only this one small write leaves it, with a swipe id the app made, so a retry is stored once.
- Every swipe is logged. The swipe service writes every swipe to the swipe log, which fills the swipe history, kept by the person who swiped. A pass would end here.
- One transaction for the pair. A like also updates the record of the pair, Meera and Arjun, on the shard the pair's key picks, in one transaction.
- The other like is there. Arjun liked Meera's card an hour ago, so the same transaction finds both likes in the record and creates the match.
- It's a match. The reply tells the app that the swipe made a match, so Meera sees "It's a match" at once.
- Arjun hears about it. The match goes on to the notification and chat services, and Arjun gets a notification within seconds of Meera's like.
The example builds one deck for a woman of 29 with a maximum distance of 8 km, among 40,000 synthetic people:
// One person's deck, built the way a deck builder does it: read the index cells around her, keep the people whose
// settings and hers both allow the pair, drop anyone she swiped or blocked, rank, and label distances roughly.
// 40,000 synthetic people live on a flat 60 km square measured in kilometres; a seeded generator makes them, so every
// run prints the same.
const INDEX_KM = 4; // the cell index groups people into 4 km squares
const SNAP_KM = 1; // positions are kept only as the centre of a 1 km square
const DECK_SIZE = 100;
function generator(seed) {
const imul = Math.imul;
return () => {
seed = (seed + 0x6d2b79f5) | 0;
let t = imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const rand = generator(4111);
const pick = (items) => items[Math.floor(rand() * items.length)];
const WANTS_OF_W = ['M', 'M', 'M', 'M', 'M', 'M', 'M', 'M', 'W', 'MWN']; // mostly men, some women, a few anyone
const WANTS_OF_M = ['W', 'W', 'W', 'W', 'W', 'W', 'W', 'W', 'M', 'MWN'];
const MAX_KMS = [5, 10, 10, 15, 15, 25, 40];
const bell = (spread) => (rand() + rand() + rand() + rand() - 2) * spread; // a bump around 0, no Math.log needed
const snap = (v) => (Math.floor(v / SNAP_KM) + 0.5) * SNAP_KM;
const cellOf = (x, y) => `${Math.floor(x / INDEX_KM)},${Math.floor(y / INDEX_KM)}`;
const km = (a, b) => Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
const n = (v) => String(v).replace(/\B(?=(\d{3})+(?!\d))/g, ','); // 18460 -> 18,460
// People: most live in the city around (0, 0), the rest anywhere in the square.
const people = [];
for (let id = 1; id <= 40000; id++) {
const inCity = rand() < 0.75;
const x = inCity ? bell(14) : (rand() - 0.5) * 60;
const y = inCity ? bell(14) : (rand() - 0.5) * 60;
const gender = rand() < 0.47 ? 'W' : rand() < 0.95 ? 'M' : 'N';
const wants = gender === 'W' ? pick(WANTS_OF_W) : gender === 'M' ? pick(WANTS_OF_M) : 'MWN';
const age = 18 + Math.floor(32 * Math.min(rand(), rand()));
people.push({
id, age, gender, wants,
ageMin: Math.max(18, age - 2 - Math.floor(rand() * 6)), ageMax: age + 2 + Math.floor(rand() * 9),
maxKm: pick(MAX_KMS), hoursAgo: Math.floor(rand() * 72),
x: snap(x), y: snap(y), // what the location service keeps: never the exact position
});
}
const index = new Map(); // cell -> ids of the people active there in the last 3 days
for (const p of people) {
const c = cellOf(p.x, p.y);
if (!index.has(c)) index.set(c, []);
index.get(c).push(p.id);
}
const byId = (id) => people[id - 1];
const her = { id: 0, age: 29, gender: 'W', wants: 'M', ageMin: 27, ageMax: 35, maxKm: 8, x: snap(1.3), y: snap(-0.6) };
const swiped = new Set(Array.from({ length: 6000 }, () => 1 + Math.floor(rand() * 40000)));
const blocked = new Set([2286, 31407]);
// 1. Read the cells: hers and enough rings of neighbours to cover her maximum distance.
const rings = Math.ceil(her.maxKm / INDEX_KM);
const [cx, cy] = cellOf(her.x, her.y).split(',').map(Number);
let read = [];
for (let dx = -rings; dx <= rings; dx++) for (let dy = -rings; dy <= rings; dy++) read.push(...(index.get(`${cx + dx},${cy + dy}`) ?? []));
const cells = (2 * rings + 1) ** 2;
console.log(`cells read: ${cells} (her ${INDEX_KM} km cell and ${rings} rings around it), people in them: ${n(read.length)}`);
// 2. Filter: every rule holds in both directions, so neither person sees someone who could never see them.
const steps = [
['within her 8 km', (p) => km(her, p) <= her.maxKm],
['genders wanted both ways', (p) => her.wants.includes(p.gender) && p.wants.includes(her.gender)],
['ages wanted both ways', (p) => p.age >= her.ageMin && p.age <= her.ageMax && her.age >= p.ageMin && her.age <= p.ageMax],
['within their own distance', (p) => km(her, p) <= p.maxKm],
['not swiped or blocked', (p) => !swiped.has(p.id) && !blocked.has(p.id)],
];
let left = read.map(byId);
for (const [name, keep] of steps) {
left = left.filter(keep);
console.log(` ${name.padEnd(27)} ${n(left.length).padStart(6)}`);
}
// 3. Rank: near and recently active first (6 hours away from the app count like 1 km more); keep a deck of 100.
const score = (p) => km(her, p) + p.hoursAgo / 6;
const deck = left.slice().sort((a, b) => score(a) - score(b) || a.id - b.id).slice(0, DECK_SIZE);
const label = (p) => `${Math.max(1, Math.round(km(her, p)))} km away`; // whole km between 1 km squares
console.log(`\ndeck: ${deck.length} of ${n(left.length)} eligible people; the first 6 cards:`);
for (const p of deck.slice(0, 6)) console.log(` #${String(p.id).padStart(5)} age ${p.age} ${label(p).padEnd(10)} active ${p.hoursAgo} h ago`);
const shown = new Map();
for (const p of deck) shown.set(label(p), (shown.get(label(p)) ?? 0) + 1);
console.log(`the deck's distance labels: ${[...shown].sort((a, b) => parseInt(a[0]) - parseInt(b[0])).map(([l, c]) => `${c} × ${l.replace(' away', '')}`).join(', ')}`);
// 4. Check: a scan of all 40,000 people finds exactly the same eligible set, so the cells missed no one.
const scan = people.filter((p) => steps.every(([, keep]) => keep(p)));
const same = scan.length === left.length && scan.every((p) => left.includes(p));
console.log(`\nfull scan of ${n(people.length)} people: ${n(scan.length)} eligible, ${same ? 'the same people' : 'DIFFERENT people'}; the cells held ${(100 * read.length / people.length).toFixed(1)} % of everyone`); Output
cells read: 25 (her 4 km cell and 2 rings around it), people in them: 18,499 within her 8 km 11,283 genders wanted both ways 5,127 ages wanted both ways 1,397 within their own distance 1,310 not swiped or blocked 1,138 deck: 100 of 1,138 eligible people; the first 6 cards: #25031 age 27 1 km away active 0 h ago #32687 age 28 1 km away active 1 h ago #19066 age 30 1 km away active 0 h ago #21023 age 30 1 km away active 0 h ago # 6503 age 27 1 km away active 3 h ago # 439 age 29 1 km away active 5 h ago the deck's distance labels: 25 × 1 km, 22 × 2 km, 19 × 3 km, 21 × 4 km, 13 × 5 km full scan of 40,000 people: 1,138 eligible, the same people; the cells held 46.2 % of everyone
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node deck_builder.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
The 25 cells held 18,499 people, and the filters left 1,138. The two-way rules did most of the work: 5,127 men within 8 km want women, but only 1,397 of them are in her age range and also want someone of 29. A scan of all 40,000 people finds the same 1,138, so reading cells missed no one. The labels are whole kilometres between square centres, from 1 to 5 km.
Checkpoint
Check: The six records and their keys · Decks ahead, swipes as small writes
Practise: filter a deck in both directions
Write the filter a deck builder runs on every candidate:
Exercise · Easy · Python
Filter and rank a deck in both directions
Write the two functions in deck.py that a deck builder runs on every candidate it reads from the cell index. A person is a dictionary such as {"id": 7, "age": 31, "gender": "M", "wants": "W", "age_min": 26, "age_max": 34, "max_km": 10, "hours_ago": 5}; wants holds the letters of every gender the person wants to see.
eligible(viewer, candidate, km) returns True when the candidate may appear in the viewer's deck, where km is the distance between their squares. Every rule holds in both directions:
- each person's gender is in the other's
wants; - each person's age is from the other's
age_mintoage_max, both ends included; kmis at most the viewer'smax_kmand at most the candidate'smax_km;- nobody appears in their own deck.
build_deck(viewer, candidates, swiped, blocked, size) takes (person, km) pairs and returns the ids of the deck: the eligible candidates whose ids are in neither swiped nor blocked, nearest first, then the most recently active (the smaller hours_ago), then the smaller id, and at most size of them.
Starter code · deck.py
def eligible(viewer, candidate, km):
"""True when the candidate may appear in the viewer's deck; km is the distance between their squares."""
# Replace this line with your code.
return True
def build_deck(viewer, candidates, swiped, blocked, size):
"""The ids of the viewer's deck from (person, km) pairs: eligible, not swiped or blocked, best first."""
# Replace this line with your code.
return [person["id"] for person, km in candidates][:size] The sample tests · test_deck.py
from deck import build_deck, eligible
HER = {"id": 1, "age": 29, "gender": "W", "wants": "M", "age_min": 27, "age_max": 35, "max_km": 8, "hours_ago": 0}
def person(id, **changes):
base = {"id": id, "age": 30, "gender": "M", "wants": "W", "age_min": 25, "age_max": 34, "max_km": 15, "hours_ago": 3}
base.update(changes)
return base
def test_a_good_pair_is_eligible():
"""both people's settings accept each other"""
assert eligible(HER, person(2), 3) is True
def test_genders_both_ways():
"""she wants men, and the man must also want women"""
assert eligible(HER, person(2, gender="W", wants="W"), 3) is False
assert eligible(HER, person(2, wants="M"), 3) is False
assert eligible(HER, person(2, wants="MWN"), 3) is True
def test_ages_both_ways_ends_included():
"""her range is 27 to 35, and her age 29 must be in his range"""
assert eligible(HER, person(2, age=27), 3) is True
assert eligible(HER, person(2, age=35), 3) is True
assert eligible(HER, person(2, age=26), 3) is False
assert eligible(HER, person(2, age=36), 3) is False
assert eligible(HER, person(2, age_min=29), 3) is True
assert eligible(HER, person(2, age_max=29), 3) is True
assert eligible(HER, person(2, age_min=30), 3) is False
assert eligible(HER, person(2, age_max=28), 3) is False
def test_distance_within_both_limits():
"""at most her 8 km and at most his own maximum"""
assert eligible(HER, person(2), 8) is True
assert eligible(HER, person(2), 9) is False
assert eligible(HER, person(2, max_km=5), 5) is True
assert eligible(HER, person(2, max_km=5), 6) is False
def test_never_yourself():
"""the viewer is not a candidate in her own deck"""
assert eligible(HER, dict(HER, gender="M", wants="W"), 0) is False
def test_deck_filters_swiped_and_blocked():
"""swiped and blocked ids never come back"""
candidates = [(person(2), 3), (person(3), 2), (person(4), 1), (person(5, age=40), 1)]
assert build_deck(HER, candidates, swiped={3}, blocked={4}, size=10) == [2]
def test_deck_order_and_size():
"""nearest first, then the most recently active, then the smaller id; at most size cards"""
candidates = [
(person(10, hours_ago=1), 4),
(person(11, hours_ago=9), 2),
(person(12, hours_ago=2), 2),
(person(13, hours_ago=2), 2),
(person(14, hours_ago=0), 7),
]
assert build_deck(HER, candidates, swiped=set(), blocked=set(), size=10) == [12, 13, 11, 10, 14]
assert build_deck(HER, candidates, swiped=set(), blocked=set(), size=3) == [12, 13, 11] A hint
Write one helper, allows(a, b), that answers whether person a's settings accept person b: the gender, the age range and nothing about the distance. Then eligible is allows(viewer, candidate) and allows(candidate, viewer) plus the two distance limits and the check on the ids. For the order, sorted accepts a key that returns a tuple, such as (km, hours_ago, id), and compares the tuples item by item.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Then practise, weakest idea first:
Practice that adapts to you
Practise this lesson
9 questions about 10 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
The deep dives this design must answer
A round then probes these questions:
- Two likes at once: the like and the check share one record per pair, so exactly one of two simultaneous likes creates the match.
- Crowded cities: a city-centre cell holds far more people than a rural one, which changes how a build reads it.
- Fresh decks: a deck is rebuilt when it runs low, when the person moves or changes settings, and when it ages.
- Past swipes: a Bloom filter: A small bit array built from a set of keys that answers "definitely not here" or "maybe here", never a false no. LSM engines keep one per file to skip the files that lack a key. More in “Storage engines: B-trees vs LSM-trees” of each person’s swiped ids keeps profiles from coming back cheaply.
- Location privacy: distances come from coarse squares and are rounded, so the most anyone learns is a square.
- Blocks: they are checked when cards are served and messages sent, not only when decks are built.
- Scale: the bottlenecks move at 10 × and 100 × the swipes.
Exactly one match for two likes, reproduced Pro: opens with a Pro, Premium or Ultimate pass
Two people who like each other within the same few milliseconds break most first designs. This part runs every order in which two such likes can interleave against four designs, shows which ones always create exactly one match, and proves why.
In this part:
- Every interleaving of two likes, measured
- Why the pair record is always right: the invariant and its proof
- Where the pair records live, with numbers
1 diagram · 2 runnable examples (Python) · about 12 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Deck builds in crowded cities, measured and sized Pro: opens with a Pro, Premium or Ultimate pass
A deck build costs little in the countryside and a great deal in a city centre at night, because the same area holds far more people. This part measures two ways of reading the cell index, then sizes the deck pipeline around the cheaper one.
In this part:
- Every cell within reach against rings that stop early, measured
- Hot cells at night, and how to split them
- Sizing the deck pipeline, with numbers
- When a deck is rebuilt
1 runnable example (JavaScript) · about 6 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Location privacy, safety, failure drills and the level rubric Pro: opens with a Pro, Premium or Ultimate pass
The distance on a card is the most sensitive number in the system, because a few of them can place a person in one building. This part runs that attack against three ways of showing distance, then covers blocks, failure drills, scale and what each level is expected to cover.
In this part:
- Finding someone from the distances shown, reproduced
- What the server keeps, uses and shows
- Blocks and reports that take effect at once
- Failure drills
- Bottlenecks at 10× and 100×, with numbers
- What each level is expected to cover
- A sample answer in ten minutes
- A 45-minute self-mock
1 runnable example (Python) · about 7 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
Choose your opening move for each brief:
Checkpoint
Your first decision
-
Mid to senior
Two people like each other within the same millisecond. Likes are stored on 64 shards by the person who swipes, and each server checks for the other like after storing its own. Show how the match can be lost or duplicated, and design detection that always creates exactly one.
-
Senior to staff
A security researcher reports that three fake accounts can find where any of your users lives, using only the distance your app shows on each card. Explain the attack, and redesign how distances are computed and shown.
-
Senior to staff
On a festival night, deck builds in the centre of a city of 10 million people take seconds and time out, while the rest of the country is fine. Find the likely cause and fix it without making decks worse.
Key takeaways
- Build decks ahead of time from the cells around a person, with every filter applied in both directions.
- A deck build reads thousands of people to keep a hundred: that read, not the swipe rate, is the heavy work.
- A swipe is a small write to a log; a like also updates one record for the pair.
- Keep only coarse positions: 1 km squares, and distances in whole kilometres.
References
- Elasticsearch documentation: Geohash grid aggregation (cell dimensions) (Elastic)
- H3 documentation: Tables of cell statistics across resolutions (H3 project)
- Redis documentation: Geospatial data type (Redis)
- PostgreSQL documentation: INSERT, the ON CONFLICT clause (The PostgreSQL Global Development Group)
- SQLite documentation: UPSERT (SQLite)
- sqlite3: DB-API 2.0 interface for SQLite databases (Cursor.rowcount) (Python Software Foundation)
- CIPA DC-008: Exchangeable image file format for digital still cameras (Exif) (Camera & Imaging Products Association (CIPA))
- The Digital Personal Data Protection Act, 2023 (No. 22 of 2023) (Ministry of Electronics and Information Technology, Government of India)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress