Financial Close Service: Overview and Requirements
A financial close service manages the end-of-period accounting process: locking the accounting period to prevent backdated entries, posting accrual and adjustment journal entries, computing the trial balance, and coordinating sign-off from finance stakeholders before the period is officially closed. Correctness and auditability are paramount; the system must guarantee that the trial balance always satisfies the accounting equation and that no entry escapes the audit trail.
Functional Requirements
- Lock an accounting period to prevent new journal entries from being posted with a date in the locked period.
- Post accrual journal entries for expenses incurred but not yet invoiced, and revenue earned but not yet billed.
- Compute a trial balance across all accounts for the period, verifying that total debits equal total credits.
- Route the trial balance through a configurable multi-step sign-off workflow before marking the period as closed.
- Support period re-opening with audit logging when a prior-period adjustment is required.
Non-Functional Requirements
- Period lock must be enforced atomically: no entry posted after the lock timestamp may carry a date within the locked period.
- Trial balance computation must be consistent: it reflects all and only the journal entries posted before the lock timestamp.
- All journal entries, period state transitions, and sign-off decisions must be immutably logged.
- The service must support concurrent close operations across multiple subsidiary ledgers without interference.
Data Model
- AccountingPeriod: period_id, ledger_id, period_start, period_end, status (open | locked | under_review | closed | reopened), lock_timestamp, closed_at, version.
- Account: account_id, ledger_id, code, name, type (asset | liability | equity | revenue | expense), normal_balance (debit | credit), parent_account_id.
- JournalEntry: entry_id, ledger_id, period_id, entry_date, description, entry_type (operational | accrual | adjustment | reversal), posted_by, posted_at, reversed_by_entry_id (nullable), status (draft | posted | reversed).
- JournalLine: line_id, entry_id, account_id, debit_amount, credit_amount, currency, memo.
- TrialBalance: trial_balance_id, period_id, computed_at, rows (account_id, opening_balance, period_debits, period_credits, closing_balance), total_debits, total_credits, balanced (bool).
- SignOffStep: step_id, period_id, step_order, role, assignee_user_id, status (pending | approved | rejected), decision_at, comment.
Period Lock Mechanism
Period locking is enforced at the database level using a check constraint and at the application level using the period status. When a lock is requested:
- The service updates AccountingPeriod status to locked and records the lock_timestamp using optimistic locking on the version column.
- All journal entry insert paths check the period status before writing. If the target period is locked, the insert is rejected with a clear error.
- For databases that support it, a row-level check constraint on JournalEntry validates that the entry_date falls within the period_start and period_end of a non-locked period, providing a second enforcement layer independent of application code.
Accrual Journal Entries
Accruals are posted automatically from accrual schedules configured per account or cost center. An accrual schedule defines a recurring pattern — for example, monthly rent expense — and the service generates the corresponding journal entry at period end:
- A debit to the expense account and a credit to the accrued liabilities account are posted as a single balanced entry.
- A reversal entry dated the first day of the next period is automatically created and held in draft status, to be posted when that period opens, unwinding the accrual once the actual invoice arrives.
- Manual accruals submitted by finance staff are validated for balance (debits must equal credits) before acceptance.
Trial Balance Computation
The trial balance is computed as an aggregate query over JournalLine joined to JournalEntry filtered to the period and to status = posted. For each account the query computes the sum of debit_amount and the sum of credit_amount. The closing balance is the opening balance adjusted by net period activity consistent with the account normal balance.
The service verifies that total_debits equals total_credits within a tolerance of zero (for same-currency ledgers) before persisting the TrialBalance record. If they do not match, the close is halted and an alert is raised indicating a data integrity issue requiring investigation before proceeding.
Sign-Off Workflow
Once the trial balance is computed and balanced, the period enters under_review status and the sign-off workflow begins. SignOffSteps are created in order according to the period configuration, typically: controller review, then CFO approval for periods above a materiality threshold.
- Each assignee receives a notification with a link to the trial balance and a drill-down to individual journal entries.
- An approval advances the workflow to the next step. A rejection returns the period to locked status with a comment, allowing finance staff to post adjustments and recompute the trial balance.
- When all steps are approved, the period status transitions to closed and the closed_at timestamp is recorded.
API Design
POST /periods/{id}/lock— lock the period, preventing new entries.POST /periods/{id}/trial-balance— trigger trial balance computation and return the result.GET /periods/{id}/trial-balance— retrieve the most recent trial balance with account-level detail.POST /journal-entries— post a journal entry; validates balance and period status.POST /periods/{id}/sign-off/{step_id}— approve or reject a sign-off step with a comment.POST /periods/{id}/reopen— reopen a closed period with a mandatory reason, subject to elevated authorization.GET /periods/{id}/audit-log— retrieve all state transitions and journal entry events for the period.
Scalability and Observability
Trial balance computation is a read-only aggregate query and can be run against a read replica to avoid impacting the write path. For ledgers with many millions of journal lines, a materialized account balance table updated incrementally on each journal post reduces trial balance computation from a full table scan to a simple account-level read. Key metrics include: time to complete trial balance computation, number of accrual entries posted per period, sign-off step duration, period-close cycle time from lock to closed, and count of prior-period adjustments requiring re-open per quarter.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How do you implement period locking to prevent backdated entries during financial close?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A fiscal_periods table holds each period's status (OPEN, CLOSING, LOCKED). A DB check constraint on the journal_entries table enforces that the entry's period_id references a period where status = 'OPEN'. Once the close process sets a period to LOCKED, the constraint rejects any new insert targeting that period at the database level — no application code can bypass it. An ADJUSTING status allows restricted post-close corrections by authorized roles only, enforced via row-level security policies.”
}
},
{
“@type”: “Question”,
“name”: “How do accrual entries and auto-reversals work in a financial close system?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Accruals are journal entries posted in period P to recognize revenue or expense before cash settles. Each accrual row carries a reversal_date field. A close job scans for accruals where reversal_date falls in the newly opened period P+1 and automatically posts mirror entries with negated debit/credit amounts, tagged as AUTO_REVERSAL with a foreign key back to the original accrual. This ensures the income statement effect is confined to the correct period without manual intervention.”
}
},
{
“@type”: “Question”,
“name”: “How is a trial balance aggregated efficiently at close time?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A trial balance is a SUM(debit) – SUM(credit) grouped by account_id for a given period. For large ledgers this query is pre-materialized incrementally: a period_balances table is updated via triggers or CDC events as journal lines are posted, accumulating running totals. At close time the trial balance read is a simple table scan of period_balances rather than a full aggregation over millions of journal lines. A reconciliation check asserts that SUM of all balances equals zero (double-entry invariant) before the period transitions to LOCKED.”
}
},
{
“@type”: “Question”,
“name”: “How do you model a multi-step sign-off workflow for financial close?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Sign-off steps are defined in a close_checklist_templates table (e.g., Controller review, CFO approval, Auditor sign-off) with an ordering field. At period open, rows are instantiated into close_checklist_items for that period, each with status PENDING and an assigned_role. A step can only transition to APPROVED after all prior steps are APPROVED — enforced by a before-update trigger that checks the predecessor row. The period status advances to LOCKED only when all checklist items reach APPROVED, making the workflow both auditable and tamper-resistant.”
}
}
]
}
See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering
See also: Coinbase Interview Guide