← Back to blog Persona OA 2026 Real Questions: CodeSignal In-Memory Database Level 1-4 Complete Walkthrough
Persona

Persona OA 2026 Real Questions: CodeSignal In-Memory Database Level 1-4 Complete Walkthrough

2026-05-15

Persona Identities, a rising Y Combinator company in the identity verification space, is hiring aggressively for Backend / Full-stack roles in 2026. Their OA does NOT follow the LeetCode style — they use CodeSignal's "four-level progressive task" framework, where one prompt unlocks four levels and your earlier code must remain compatible with later levels. The classic prompt is the "In-Memory Database", now nearly a Persona OA signature, also seen at Ramp and Square.

This article is based on a recent Persona OA debrief from candidates. We'll break down Levels 1-4 with incrementally refactorable Python implementations, and call out exactly where each level traps people.

Persona OA Overview

Dimension Detail
Platform CodeSignal General Coding Framework
Duration 90 minutes (IDE familiarization included)
Question 1 prompt × 4 progressive levels
Test cases ~25-30 total
Language Python / Java / Go / JS — Python has highest pass rate
Scoring 100 pts (L1=20, L2=25, L3=25, L4=30)

CodeSignal's nuance: each level only reveals its own tests, and you must pass the current level to unlock the next. If you hard-code value types in Level 1, you'll be forced into a painful rewrite by Level 3.

Level 1: Basic Key-Value Operations

Problem

Implement an in-memory database supporting:

All return values must be strings.

Approach

Natural two-level dict: db[key][field] = value. The key trick is do not create empty dicts eagerly — that pollutes backups in Level 4.

Python Solution

class InMemoryDB:
    def __init__(self):
        self.db = {}

    def set(self, key, field, value):
        self.db.setdefault(key, {})[field] = value
        return ""

    def get(self, key, field):
        return self.db.get(key, {}).get(field, "")

    def delete(self, key, field):
        if key in self.db and field in self.db[key]:
            del self.db[key][field]
            if not self.db[key]:
                del self.db[key]
            return "true"
        return "false"

Time: amortized O(1) per op Space: O(K·F)

Level 2: Scan and Prefix Query

Problem

Add:

Approach

Just sorted(items()). Do NOT pre-sort using a SortedDict — Level 4 will do bulk rollbacks, so simpler structures win.

Python Solution

    def scan(self, key):
        if key not in self.db:
            return ""
        items = sorted(self.db[key].items())
        return ", ".join(f"{f}({v})" for f, v in items)

    def scan_by_prefix(self, key, prefix):
        if key not in self.db:
            return ""
        items = sorted(
            (f, v) for f, v in self.db[key].items() if f.startswith(prefix)
        )
        return ", ".join(f"{f}({v})" for f, v in items)

Time: O(F log F)

Level 3: TTL Expiration

Problem

Every command now carries a timestamp:

Expiration is strict: ts >= expire_at means expired.

Approach

Change value from string to (value, expire_at) where expire_at = None means permanent. Route every read through a single helper _alive(key, field, ts) so the check isn't scattered across six methods.

Python Solution

class InMemoryDB:
    def __init__(self):
        self.db = {}  # key -> field -> (value, expire_at|None)

    def _alive(self, key, field, ts):
        rec = self.db.get(key, {}).get(field)
        if rec is None:
            return None
        value, expire_at = rec
        if expire_at is not None and ts >= expire_at:
            return None
        return value

    def set_at(self, key, field, value, ts):
        self.db.setdefault(key, {})[field] = (value, None)
        return ""

    def set_at_with_ttl(self, key, field, value, ts, ttl):
        self.db.setdefault(key, {})[field] = (value, ts + ttl)
        return ""

    def get_at(self, key, field, ts):
        v = self._alive(key, field, ts)
        return "" if v is None else v

    def delete_at(self, key, field, ts):
        if self._alive(key, field, ts) is None:
            return "false"
        del self.db[key][field]
        return "true"

    def scan_at(self, key, ts):
        if key not in self.db:
            return ""
        live = sorted(
            (f, v) for f, (v, exp) in self.db[key].items()
            if exp is None or ts < exp
        )
        return ", ".join(f"{f}({v})" for f, v in live)

Pitfall: many people eagerly evict expired entries — this loses data when Level 4 restores to a past timestamp. Lazy expiration is correct.

Level 4: Backup & Restore

Problem

Approach

Two critical bits:

  1. Snapshot format: not a shallow copy. Store (field, value, remaining_ttl) where remaining_ttl = expire_at - backup_ts; permanent entries use None.
  2. Restore recomputes expire_at: new_expire = ts + remaining_ttl

Most failures: copying the raw expire_at back — every restored TTL instantly expires.

Python Solution

class InMemoryDB:
    def __init__(self):
        self.db = {}
        self.backups = {}  # backup_ts -> snapshot

    # ... reuse Level 1-3 methods ...

    def backup(self, ts):
        snapshot = {}
        for key, fields in self.db.items():
            live = {}
            for f, (v, exp) in fields.items():
                if exp is None:
                    live[f] = (v, None)
                elif ts < exp:
                    live[f] = (v, exp - ts)
            if live:
                snapshot[key] = live
        self.backups[ts] = snapshot
        return str(len(snapshot))

    def restore(self, ts, backup_ts):
        if backup_ts not in self.backups:
            return ""
        snap = self.backups[backup_ts]
        new_db = {}
        for key, fields in snap.items():
            new_fields = {}
            for f, (v, remaining) in fields.items():
                exp = None if remaining is None else ts + remaining
                new_fields[f] = (v, exp)
            new_db[key] = new_fields
        self.db = new_db
        return ""

Level 4 Common Mistakes

  1. Using copy.deepcopy(self.db) as the snapshot — drags expired entries back in
  2. Not recalculating expire_at on restore — every TTL is stale
  3. Letting SET_AT mutate prior backups — backups must stay read-only
  4. Iterating twice (filter expired, then compute remaining) — merge into one pass

Persona Prep Roadmap

Stage Recommended Practice
CodeSignal warm-up LC 146 LRU Cache, LC 460 LFU Cache
State machines LC 432, LC 1206 Skiplist
TTL thinking Redis EXPIRE docs + LC 362 Hit Counter
Snapshot/restore git-style incremental snapshots, LC 1166 File System

Pacing: nail Level 1 in 15 min, Levels 2-3 in 20-25 min each, leave 25-30 min for Level 4. Don't over-abstract Level 1 — CodeSignal scores on test cases, not "clean code."


FAQ

Q1: How hard is Persona OA vs LeetCode Medium?

Per-question difficulty does not exceed LC Medium, but the real test is incremental refactoring — keeping earlier code compatible across four levels under a 90-min clock. If you hard-code value types in Level 1, Level 3 becomes brutal.

Q2: What platform and duration?

CodeSignal General Coding Framework, 90 minutes. Note: there is also a separate 60-min General Coding Assessment (GCA) earlier in the funnel — that one is pure LeetCode-style. In-Memory Database is the Practical Task that follows.

Q3: Are Backend and Full-stack OAs different?

The OA itself is identical. Full-stack adds a React/TypeScript practical after OA, while backend goes into system design. No need to specialize at OA stage.

Q4: Which language has the highest pass rate?

Python > Go > Java > JS. CodeSignal's Python template plus dict-of-dicts ergonomics minimize syntax overhead. Avoid Java — nested generic Map types eat a huge chunk of your 90 minutes.

Q5: What if backup_ts in RESTORE doesn't exist?

The problem statement guarantees the backup_ts was previously created — but restoring an older backup after several newer ones is a sneaky hidden case. Use a dict keyed by ts, not a list.

Q6: How fast does Persona move after OA?

Most 1point3acres reports say 3-7 business days to the next round. Persona moves fast — hiring managers often send calendar invites directly. Check your spam folder.


Preparing Persona / Ramp / Square CodeSignal-style four-level OAs?

The four-level format tests engineering judgment and state management, not algorithmic ceiling. If you want our curated four-level question bank (Persona, Ramp, Stripe, Square, Brex) or a 1-on-1 walkthrough of Level 3-4 state machines, reach out.

Add WeChat Coding0201 or contact us.

Contact

Email: [email protected] Telegram: @OAVOProxy