In IndexedDB, what is an object store, what does a store's keyPath do, and what changes when the store is created with autoIncrement: true?
answer
- not tables — keyed collections
- the key can live inside the record
- or be handed in from outside
- a generator hands out 1, 2, 3…
- add asserts new, put overwrites
basics
~20 sAn object store is IndexedDB's container for records: a keyed collection of values, closer to a key-value map than a SQL table. keyPath names the property inside each record used as its primary key; autoIncrement: true makes the store generate increasing integer keys instead.
solid answer
~50 sIndexedDB has no tables or rows. A database contains named **object stores**, and each store holds records: a primary key plus a stored value. How the key is produced is decided once, when the store is created inside an upgrade. If you pass a `keyPath` — `db.createObjectStore('orders', { keyPath: 'id' })` — the key is *in-line*: the browser reads it out of the record itself at that path, so every record must carry an `id`. With no `keyPath`, keys are *out-of-line* and you supply one explicitly as the second argument, `store.put(value, key)`. `autoIncrement: true` attaches a key generator to the store, which hands out increasing integers starting at 1; combined with a `keyPath`, the generated key is also written into the stored record at that path. Keys must be a number, string, `Date`, binary value, or an array of those — not a boolean, `null`, or a plain object.
code
javascript · 24 linesconst request = indexedDB.open('shop', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// In-line key read from the record, generated when absent.
db.createObjectStore('orders', { keyPath: 'id', autoIncrement: true });
// Out-of-line key: supplied at write time.
db.createObjectStore('files');
};
request.onsuccess = (event) => {
const db = event.target.result;
const tx = db.transaction(['orders', 'files'], 'readwrite');
const orders = tx.objectStore('orders');
orders.add({ total: 30 }); // stored as { id: 1, total: 30 }
const files = tx.objectStore('files');
files.put(new Blob(['hi']), 'note.txt'); // key is the 2nd argument
tx.oncomplete = () => db.close();
};go deeper
Be able to say that IndexedDB holds records in object stores, that keyPath points at the property used as the primary key, and that autoIncrement makes the browser number records for you.
Explain in-line versus out-of-line keys and why the two cannot be mixed on one store, what compound key paths buy you, and the difference between add and put when the key already exists.
Show judgment about key design: why device-generated auto-increment ids collide once data syncs, when a natural or server-assigned id belongs in the key path, and what it costs to change a store's key configuration after ship.
Own the identity model end to end — how client-minted keys, server ids, and compound keys interact with sync, conflict resolution, and the migration you will owe yourself when the record shape changes.
## What IndexedDB actually is IndexedDB is the browser's real database: an asynchronous, transactional, origin-scoped store designed for large structured data. It has no query language and no tables. A named database contains named **object stores**, and each object store contains **records**, where a record is a (primary key, value) pair. The mental model closest to the truth is an ordered map: keys are sorted, and every read is either "give me the record at this key" or "walk the keys in this range". A database also carries an integer **version**. Object stores can only be created or destroyed while a version upgrade is running, which is why store definitions live inside the `upgradeneeded` handler of `indexedDB.open(name, version)` and nowhere else. ## Object stores ```js const req = indexedDB.open('shop', 1); req.onupgradeneeded = (event) => { const db = event.target.result; db.createObjectStore('orders', { keyPath: 'id' }); }; ``` `IDBDatabase.createObjectStore(name, options)` takes exactly two interesting options: `keyPath` and `autoIncrement`. Together they decide where a record's primary key comes from. That decision is permanent for the life of the store — changing it means creating a differently-configured store in a later version and copying the data across. ## In-line keys: keyPath When `keyPath` is set, the key is **in-line**: it lives inside the value, and the browser extracts it on every write. `keyPath: 'id'` reads the `id` property. A dotted path such as `'user.id'` reaches into a nested object. An array key path such as `['userId', 'createdAt']` builds a **compound key**, which sorts by the first component, then the second. Two consequences follow. First, a record that is missing the key path property is rejected — the write request fails with a `DataError`. Second, you must *not* also pass an explicit key: `store.put(value, 42)` against a store that has a `keyPath` is also a `DataError`, because the store already knows where the key comes from. ## Out-of-line keys With no `keyPath`, the key lives outside the value and you provide it yourself: ```js const store = tx.objectStore('blobs'); store.put(someBlob, 'avatar/42'); // key is the second argument ``` This is the right shape when the stored value is not a plain object you control — a `Blob`, an `ArrayBuffer`, a number — or when the natural key is not a property of the data. ## autoIncrement and the key generator `autoIncrement: true` gives the store a **key generator**. Each generated key is an integer, starting at 1 and increasing. If the store also has a `keyPath` and the record you write lacks that property, the generated key is written into the stored record at that path — so after `store.add({ total: 30 })` on a `{ keyPath: 'id', autoIncrement: true }` store, the record you read back has `id: 1`. You may still supply your own key; an explicit larger numeric key pushes the generator up, so later generated keys stay above it. Auto-increment is convenient for local-only data. It is a poor choice for records that also exist on a server, because two devices working offline will both mint key 1 for different records; a server-assigned id or a UUID as an in-line `keyPath` avoids that collision entirely. ## Keys are typed and ordered Valid key types are: numbers, strings, `Date`, `ArrayBuffer` and typed arrays, and arrays of those. Booleans, `null`, `undefined`, and plain objects are not valid keys and produce a `DataError`. Keys have a total order defined across types (numbers before dates before strings before binary before arrays), which is exactly what makes ordered iteration and range queries possible: without a defined order there is no "next key". ## add versus put `store.add(value)` is insert-only: if a record already exists at that key, the request fails with a `ConstraintError`. `store.put(value)` is an upsert: it replaces any existing record at that key. Reaching for `put` everywhere is common and usually fine, but `add` is how you assert "this must be new" and get told when it is not. ## What people get wrong Treating a store like a SQL table and expecting to query arbitrary fields is the big one — an object store is only queryable by its primary key until you add a secondary index. Assuming `keyPath` validates or reshapes the record is another: it only locates the key. And assuming auto-increment keys are stable identities across devices leads directly to sync collisions.
- When would you deliberately choose out-of-line keys over a keyPath?When the value is not a plain object you control — a `Blob`, an `ArrayBuffer`, a string — there is nowhere sensible to put an `id` property, so the key has to travel separately. It is also the honest choice when the key is derived from context rather than from the data, such as a cache path or a composed lookup string.
- Why are auto-increment keys a bad primary key for records that also live on a server?The generator is per-store and per-device. Two clients working offline both mint key 1 for unrelated records, so the keys collide the moment the data syncs and neither side can tell which record is which. Use a server-assigned id or a UUID as an in-line key path; keep auto-increment for local-only data such as a queue of pending writes.
- What happens if you write a record that has no property at the store's keyPath?The request fails with a `DataError` and, if the error goes unhandled, it aborts the whole transaction. The one exception is a store that also has `autoIncrement: true`: there the browser generates a key and writes it into the record at the key path, so the missing property is filled in rather than rejected.
An object store is an ordered map, not a spreadsheet: keyPath says "use this field on the record as its map key", autoIncrement says "don't bother, I'll number them for you".
saying these in an interview costs you the question
- Calling object stores tables and expecting SQL-style queries
- Thinking keyPath validates or transforms the stored record
- Believing auto-increment keys are globally unique across devices
- Passing an explicit key to a store that already has a keyPath
- Assuming any JavaScript value can serve as a key