When a SQL-language function in PostgreSQL reaches the end of its body without executing a RETURN statement, PostgreSQL raises ERROR: control reached end of function without RETURN with SQLSTATE 2F005 and condition name function_executed_no_return_statement. This error belongs to error class 2F — SQL Routine Exception.
What This Error Means
SQLSTATE class 2F covers errors that arise during the execution of SQL routines (functions and procedures defined in SQL). The specific code 2F005 signals that a function's body completed all of its statements but never reached a RETURN. For non-void functions, a RETURN is mandatory — it is the only way the function can deliver a value back to the caller.
PostgreSQL enforces this at runtime rather than at function-creation time. This means a function body with conditional logic can be CREATEd successfully even when some code paths lack a RETURN, and the error only surfaces when execution reaches one of those incomplete paths.
When this error is raised, the transaction is aborted. Any work done within the function call (and any enclosing transaction that has not been saved via a savepoint) must be rolled back before new work can proceed.
Common Causes
Missing
RETURNin a non-void SQL function. A function declared with a return type other thanvoidmust always execute aRETURN <expression>before it exits. Forgetting to add one — especially in a short utility function — is the most common trigger.Conditional logic that leaves a code path without a
RETURN. A function may haveIF ... THEN RETURN ...; END IF;but omit anELSEbranch (or a final unconditionalRETURN). When theIFcondition evaluates toFALSE, execution falls off the end of the function without returning.RETURNinside a loop that never executes. ARETURNplaced only inside aFORorWHILEloop body will be skipped entirely if the loop iterates zero times, leaving the function without a return value.Confusing
RETURNwithRETURN NEXT/RETURN QUERYin set-returning functions. In set-returning functions (those declaredRETURNS SETOForRETURNS TABLE), rows are emitted viaRETURN NEXTorRETURN QUERY, but the function still needs a final bareRETURN;to signal end-of-set. Omitting that finalRETURN;will trigger 2F005.
How to Fix function_executed_no_return_statement
Add an unconditional
RETURNat the end of the function body. The simplest fix is to ensure every code path ends with aRETURNstatement.CREATE OR REPLACE FUNCTION get_discount(price numeric) RETURNS numeric LANGUAGE plpgsql AS $$ BEGIN IF price > 100 THEN RETURN price * 0.10; END IF; -- Without the line below, calls where price <= 100 raise 2F005 RETURN 0; END; $$;Audit all conditional branches. Review every
IF/ELSIF/ELSEchain and everyCASEexpression to confirm each branch terminates with aRETURN. A common pattern is to place a final defaultRETURNafter all conditional blocks as a safety net.Add the final bare
RETURN;to set-returning functions.CREATE OR REPLACE FUNCTION active_users() RETURNS SETOF users LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT * FROM users WHERE active = TRUE; RETURN; -- required to signal end of the result set END; $$;Declare void functions explicitly. If a function is not meant to return a value, declare
RETURNS void. PostgreSQL then does not require aRETURNstatement (though you may still useRETURN;to exit early).CREATE OR REPLACE FUNCTION log_event(msg text) RETURNS void LANGUAGE plpgsql AS $$ BEGIN INSERT INTO event_log(message, logged_at) VALUES (msg, now()); END; $$;Use
\df+orpg_procto inspect existing functions. If the error appears in a production function whose source you don't immediately have access to, retrieve it via:SELECT prosrc FROM pg_proc WHERE proname = 'your_function_name';
Additional Information
- SQLSTATE class
2F(SQL Routine Exception) contains four codes:2F000(generic SQL routine exception),2F002(modifying_sql_data_not_permitted),2F003(prohibited_sql_statement_attempted), and2F005(function_executed_no_return_statement). Of these,2F005is by far the most frequently encountered in day-to-day development. - This error is specific to SQL-language and PL/pgSQL functions. C-language functions and internal functions cannot produce it because their return handling is managed at the C level.
- Most PostgreSQL client libraries (libpq, psycopg2, psycopg3, asyncpg, node-postgres) surface this as a regular server-side error with the
2F005SQLSTATE. No special client-side handling is needed beyond standard error catching. - PostgreSQL has enforced this behaviour consistently across all currently supported major versions (12 through 17). There is no version where this check was absent.
- Static analysis tools such as
plpgsql_check(an open-source PostgreSQL extension) can detect missingRETURNpaths at function-definition time, before any runtime error occurs.
Frequently Asked Questions
Why does PostgreSQL allow me to create the function without error, but then fail at call time?
PostgreSQL only parses and syntax-checks PL/pgSQL function bodies at creation time; it does not perform full control-flow analysis. Missing-return paths are only detectable at execution, because the outcome of conditional branches depends on runtime data. The plpgsql_check extension adds the static analysis that the core system omits.
Does this error occur for void functions too?
No. Functions declared RETURNS void are exempt — PostgreSQL does not require them to execute a RETURN statement. The error is only possible when the function has a concrete return type.
My function always returns in practice, but I still get this error in production. Why?
The most common explanation is that an edge-case input triggers a code path that was never hit during testing. For example, a NULL argument may cause an IF condition to evaluate to neither TRUE nor FALSE (three-valued logic), bypassing the branch that contains the RETURN. Add an explicit ELSE or a final unconditional RETURN to cover all cases.
Can I catch this error inside the same function using an exception handler?
No. Once the function body exits without a RETURN, control returns to the PostgreSQL executor, which then raises 2F005. There is no opportunity for an exception handler inside the function itself to intercept it, because the error originates after the function body has finished executing.