Top 60 PL/SQL Interview Questions (2026)

The 60 PL/SQL questions interviewers actually ask, with direct answers, runnable code, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.

60 questions with answers

What Is PL/SQL?

Key Takeaways

  • PL/SQL is Oracle's procedural extension to SQL: it adds variables, loops, conditions, and error handling around ordinary SQL statements.
  • Code is organized into blocks with declaration, execution, and exception sections, and packaged into procedures, functions, packages, and triggers stored in the database.
  • Interviews test how well you understand cursors, exception handling, bulk operations, and the SQL-to-PL/SQL context switch, not just syntax.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery gets evaluated too.

PL/SQL (Procedural Language for SQL) is Oracle's procedural extension to SQL. Where plain SQL states what data you want, PL/SQL adds the how: variables, IF and CASE logic, loops, cursors, and structured error handling, all running inside the Oracle Database engine. Its unit is the block, which has an optional declaration section, a mandatory execution section between BEGIN and END, and an optional exception section for handling errors. Blocks become named, stored objects: procedures, functions, packages, and triggers that live in the database and run close to the data. According to Oracle's PL/SQL Language Reference, the language tightly integrates SQL so a query result flows straight into procedural code without leaving the server. In interviews, PL/SQL questions probe cursors, exception handling, bulk processing (BULK COLLECT and FORALL), and the cost of switching between the SQL and PL/SQL engines. This page collects the 60 questions that come up most, each with a direct answer and runnable code. Increasingly the first database round runs as a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
21Runnable PL/SQL snippets you can practice from
45-60 minTypical length of a PL/SQL technical round

Watch: Oracle PL/SQL Full Course

Video: Oracle PL/SQL Full Course (IT Expert, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your PL/SQL certificate.

Jump to quiz

All Questions on This Page

60 questions
PL/SQL Interview Questions for Freshers
  1. 1. What is PL/SQL and why does Oracle add it on top of SQL?
  2. 2. What are the sections of a PL/SQL block?
  3. 3. What is the difference between a procedure and a function?
  4. 4. What are the common PL/SQL data types?
  5. 5. How do you declare variables and constants in PL/SQL?
  6. 6. What is the %TYPE attribute and why use it?
  7. 7. What is %ROWTYPE?
  8. 8. What is a cursor in PL/SQL?
  9. 9. What is the difference between an implicit and an explicit cursor?
  10. 10. What are the cursor attributes %FOUND, %NOTFOUND, %ROWCOUNT, and %ISOPEN?
  11. 11. What is a cursor FOR loop and why is it preferred?
  12. 12. How do IF and CASE work in PL/SQL?
  13. 13. What loop types does PL/SQL support?
  14. 14. How does SELECT INTO work and what can go wrong?
  15. 15. How does exception handling work in PL/SQL?
  16. 16. The some predefined PL/SQL exceptions and when they fire.
  17. 17. What is DBMS_OUTPUT.PUT_LINE and how do you see its output?
  18. 18. How do you write comments in PL/SQL?
  19. 19. What do COMMIT, ROLLBACK, and SAVEPOINT do?
  20. 20. What is the difference between DELETE, TRUNCATE, and DROP?
PL/SQL Intermediate Interview Questions
  1. 21. What is a package and why use one?
  2. 22. What is the difference between a package specification and body?
  3. 23. What is a database trigger and what are the main types?
  4. 24. What are :OLD and :NEW in a trigger?
  5. 25. What is the difference between a statement-level and a row-level trigger?
  6. 26. What is a mutating table error and how do you avoid it?
  7. 27. Can a cursor take parameters, and why is that useful?
  8. 28. What do FOR UPDATE and WHERE CURRENT OF do?
  9. 29. What is BULK COLLECT and why does it speed things up?
  10. 30. What is FORALL and how does it differ from a FOR loop?
  11. 31. What are the collection types in PL/SQL?
  12. 32. What is a PL/SQL record and how is it different from a collection?
  13. 33. What is a REF CURSOR and when do you use it?
  14. 34. What are IN, OUT, and IN OUT parameter modes?
  15. 35. What does RAISE_APPLICATION_ERROR do?
  16. 36. How do you declare and raise a user-defined exception?
  17. 37. What do SQLCODE and SQLERRM return?
  18. 38. What is dynamic SQL and how does EXECUTE IMMEDIATE work?
  19. 39. What is an autonomous transaction?
  20. 40. When would you use a view versus a stored procedure?
PL/SQL Interview Questions for Experienced Developers
  1. 41. What is the SQL-to-PL/SQL context switch and why does it matter for performance?
  2. 42. How do you find and fix a slow PL/SQL program?
  3. 43. What is a compound trigger and what problem does it solve?
  4. 44. What is the difference between definer's rights and invoker's rights?
  5. 45. What does PRAGMA EXCEPTION_INIT do?
  6. 46. What is native compilation and how does it differ from interpreted PL/SQL?
  7. 47. What is the PL/SQL function result cache?
  8. 48. What is a pipelined table function?
  9. 49. What does the NOCOPY hint do on a parameter?
  10. 50. Why do bind variables matter for performance and security?
  11. 51. How does FORALL with SAVE EXCEPTIONS handle partial failures?
  12. 52. What does the DETERMINISTIC keyword mean on a function?
  13. 53. What is package state and how can it cause problems?
  14. 54. Why is committing inside a row-by-row loop usually a bad idea?
  15. 55. What collection methods should you know, and what is a sparse collection?
  16. 56. When should you prefer static SQL over dynamic SQL?
  17. 57. What is subprogram overloading and where is it allowed?
  18. 58. How do you schedule PL/SQL to run on a recurring basis?
  19. 59. How do you prevent SQL injection in PL/SQL dynamic SQL?
  20. 60. Design question: how would you write a PL/SQL job to process millions of rows efficiently?

PL/SQL Interview Questions for Freshers

Freshers20 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is PL/SQL and why does Oracle add it on top of SQL?

PL/SQL is Oracle's procedural extension to SQL. Plain SQL says what data you want; PL/SQL adds the how, with variables, IF and CASE branching, loops, cursors, and structured error handling, so you can express logic the database runs itself. It compiles into stored objects like procedures, functions, packages, and triggers that live in the database.

Oracle adds it so you can keep data-heavy logic close to the data. Instead of pulling every row to an application and pushing changes back, a PL/SQL block runs inside the engine, cutting network trips and keeping business rules in one place.

Key point: A one-line definition plus the 'runs close to the data' reason beats a memorized feature list. Interviewers open with this to hear how you organize an answer.

Q2. What are the sections of a PL/SQL block?

A block has three sections. The optional declaration section, introduced by DECLARE, holds variables, cursors, and types. The mandatory execution section sits between BEGIN and END and contains the actual statements. The optional exception section, starting at EXCEPTION, catches errors raised anywhere in the execution part and handles them.

Only the execution section is required. The smallest legal block is BEGIN NULL; END; which does nothing.

sql
DECLARE
  v_total NUMBER := 0;
BEGIN
  v_total := 10 + 5;
  DBMS_OUTPUT.PUT_LINE('Total: ' || v_total);
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('Something failed');
END;
/

Key point: Interviewers use this to check you know DECLARE and EXCEPTION are optional but the BEGIN...END body is not.

Q3. What is the difference between a procedure and a function?

A function must return a single value through a RETURN clause, and because it yields a value it can be called inside a SQL expression or a SELECT list. A procedure instead performs an action and returns nothing directly, though it can hand values back to the caller through OUT parameters. That return requirement is the core split.

Use a function when the goal is to compute and return a value; use a procedure when the goal is to do something, like updating rows or sending a batch.

sql
CREATE OR REPLACE FUNCTION full_name(p_first VARCHAR2, p_last VARCHAR2)
RETURN VARCHAR2 IS
BEGIN
  RETURN p_first || ' ' || p_last;
END;
/

CREATE OR REPLACE PROCEDURE raise_salary(p_id NUMBER, p_pct NUMBER) IS
BEGIN
  UPDATE employees SET salary = salary * (1 + p_pct/100) WHERE emp_id = p_id;
END;
/
ProcedureFunction
Returns a valueNo (only via OUT)Yes, mandatory RETURN
Usable in SQL SELECTNoYes, if it meets purity rules
Typical purposePerform an actionCompute and return a value
Call styleEXEC or in a blockAs an expression

Key point: The follow-up is 'can you call a procedure inside a SELECT?'. the technical answer is no, and knowing why (SELECT needs a value) shows real understanding.

Q4. What are the common PL/SQL data types?

PL/SQL uses all the SQL scalar types (NUMBER, VARCHAR2, DATE, CHAR, CLOB) and adds PL/SQL-only types like BOOLEAN, PLS_INTEGER, and BINARY_INTEGER. On top of scalars it has composite types: records that group fields, collections that hold many elements, and cursor types that point at result sets. That range is what lets one language cover both data and logic.

BOOLEAN and PLS_INTEGER matter in interviews because they're PL/SQL-only: you can't store a BOOLEAN in a table column, and PLS_INTEGER is faster than NUMBER for loop counters because it maps to native machine integers.

  • Scalar: NUMBER, VARCHAR2, DATE, CHAR, CLOB
  • PL/SQL-only scalar: BOOLEAN, PLS_INTEGER, BINARY_INTEGER
  • Composite: RECORD, associative array, nested table, VARRAY

Key point: Mentioning that BOOLEAN can't live in a table column is a small detail that indicates hands-on experience.

Q5. How do you declare variables and constants in PL/SQL?

Declare variables in the DECLARE section as name followed by a type, with an optional starting value after := or the DEFAULT keyword. To make a constant, add the CONSTANT keyword and an initial value; a constant must be initialized at declaration and can't be reassigned anywhere in the block, so the compiler rejects any later write to it.

Add NOT NULL to force a variable to always hold a value; assigning NULL then raises an error at runtime.

sql
DECLARE
  v_count   NUMBER := 0;
  v_name    VARCHAR2(50) NOT NULL := 'Asha';
  c_tax_pct CONSTANT NUMBER := 18;
BEGIN
  DBMS_OUTPUT.PUT_LINE(v_name || ' tax ' || c_tax_pct);
END;
/

Q6. What is the %TYPE attribute and why use it?

%TYPE declares a variable using the data type of an existing table column or another variable, written as v_sal employees.salary%TYPE. The variable inherits whatever type and size that column has, so you never restate the type by hand and never risk it drifting out of sync with the schema.

The benefit is that your code stays correct when the column definition changes. Widen salary from NUMBER(6) to NUMBER(8) and every %TYPE variable follows automatically, with no edits.

sql
DECLARE
  v_salary employees.salary%TYPE;
BEGIN
  SELECT salary INTO v_salary FROM employees WHERE emp_id = 101;
  DBMS_OUTPUT.PUT_LINE('Salary: ' || v_salary);
END;
/

Key point: Pair %TYPE with %ROWTYPE in your answer. Anchoring variable types to the schema is exactly the maintainability habit interviewers look for.

Q7. What is %ROWTYPE?

%ROWTYPE declares a record variable whose fields match every column of a table or the whole select list of a cursor. One variable then holds an entire row, and you reach each field with dot notation like v_emp.salary or v_emp.first_name. It's the row-level counterpart to %TYPE, which anchors a single scalar to one column.

It saves declaring each column separately and stays in sync with the table. Fetching a cursor row straight into a %ROWTYPE record is the tidiest way to loop over query results.

sql
DECLARE
  v_emp employees%ROWTYPE;
BEGIN
  SELECT * INTO v_emp FROM employees WHERE emp_id = 101;
  DBMS_OUTPUT.PUT_LINE(v_emp.first_name || ': ' || v_emp.salary);
END;
/

Q8. What is a cursor in PL/SQL?

A cursor is a pointer to the result set of a SQL query, letting PL/SQL process rows one at a time. There are two kinds. Implicit cursors are the ones Oracle opens automatically for every single SQL statement you run. Explicit cursors are ones you declare, open, fetch from, and close yourself, which gives you row-by-row control over a multi-row query.

You reach for an explicit cursor when you need to walk many rows and act on each. For a single-row lookup, SELECT INTO with its implicit cursor is simpler and shorter.

sql
DECLARE
  CURSOR c_emps IS SELECT first_name, salary FROM employees;
  v_name employees.first_name%TYPE;
  v_sal  employees.salary%TYPE;
BEGIN
  OPEN c_emps;
  LOOP
    FETCH c_emps INTO v_name, v_sal;
    EXIT WHEN c_emps%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(v_name || ': ' || v_sal);
  END LOOP;
  CLOSE c_emps;
END;
/

The explicit cursor lifecycle

1DECLARE
name the cursor and attach its SELECT query
2OPEN
run the query and position before the first row
3FETCH
pull the next row into variables, looping until %NOTFOUND
4CLOSE
release the cursor and its resources

A cursor FOR loop does all four steps for you, which is why it's the default choice.

Key point: The classic follow-up is 'implicit vs explicit'. Lead with the definition, then The kinds without being asked.

Watch a deeper explanation

Video: PL/SQL tutorial 26: Introduction to PL/SQL Cursor in Oracle Database By Manish Sharma (Manish Sharma, YouTube)

Q9. What is the difference between an implicit and an explicit cursor?

Oracle opens an implicit cursor automatically for every SQL statement you run, including SELECT INTO and every INSERT, UPDATE, and DELETE, then closes it for you. An explicit cursor is one you declare yourself and drive manually through OPEN, FETCH, and CLOSE, which gives you row-by-row control that an implicit cursor doesn't.

Use an explicit cursor when you need to loop over many rows with full control. Implicit cursors handle single-row and DML statements, and you read their outcome through attributes like SQL%ROWCOUNT and SQL%FOUND.

Implicit cursorExplicit cursor
Who opens itOracle, automaticallyYou, with OPEN
Best forSingle-row and DMLMulti-row loops
Attribute prefixSQL%cursor_name%
Control over fetchNoneFull, row by row

Key point: the key point is that even a plain UPDATE runs through an implicit cursor. That detail separates memorizers from understanders.

Q10. What are the cursor attributes %FOUND, %NOTFOUND, %ROWCOUNT, and %ISOPEN?

These four attributes report a cursor's state. %FOUND is TRUE if the last fetch returned a row; %NOTFOUND is its opposite and drives loop exits; %ROWCOUNT counts rows fetched so far; %ISOPEN tells you whether the cursor is currently open.

For explicit cursors you prefix them with the cursor name (c_emps%NOTFOUND). For the last implicit statement you prefix them with SQL (SQL%ROWCOUNT after an UPDATE tells you how many rows changed).

sql
BEGIN
  UPDATE employees SET salary = salary + 100 WHERE dept_id = 10;
  DBMS_OUTPUT.PUT_LINE('Rows updated: ' || SQL%ROWCOUNT);
END;
/

Q11. What is a cursor FOR loop and why is it preferred?

A cursor FOR loop opens a cursor, fetches each row into a loop record, runs the body once per row, and closes the cursor automatically when the rows run out, all in one compact statement. You skip the manual OPEN, the FETCH, the EXIT WHEN test, and the CLOSE that a hand-written cursor loop needs.

It's preferred because it's shorter, harder to get wrong (no forgotten CLOSE, no infinite loop from a missing EXIT), and reads clearly. The loop variable is an implicit %ROWTYPE record, so you reach columns with dot notation.

sql
BEGIN
  FOR emp IN (SELECT first_name, salary FROM employees) LOOP
    DBMS_OUTPUT.PUT_LINE(emp.first_name || ': ' || emp.salary);
  END LOOP;
END;
/

Key point: If you write a manual OPEN/FETCH/CLOSE loop when a cursor FOR loop would do, expect the interviewer to ask why. Reach for the FOR loop by default.

Watch a deeper explanation

Video: PL/SQL tutorial 30: Cursor FOR Loop In Oracle Database By Manish Sharma (Manish Sharma, YouTube)

Q12. How do IF and CASE work in PL/SQL?

IF...THEN...ELSIF...ELSE...END IF branches on boolean conditions, evaluated top to bottom until one is TRUE, then runs that branch. CASE comes in two forms: a simple CASE that matches one expression against a list of values, and a searched CASE that evaluates independent boolean conditions the way an IF chain does but more compactly.

CASE also works as an expression that returns a value, which IF can't do. Use IF for control flow and CASE when you're assigning a value based on conditions.

sql
DECLARE
  v_score NUMBER := 82;
  v_grade CHAR(1);
BEGIN
  v_grade := CASE
               WHEN v_score >= 90 THEN 'A'
               WHEN v_score >= 80 THEN 'B'
               ELSE 'C'
             END;
  DBMS_OUTPUT.PUT_LINE('Grade: ' || v_grade);
END;
/

Q13. What loop types does PL/SQL support?

Three main ones. The basic LOOP runs until an explicit EXIT or EXIT WHEN breaks out, so it always executes at least once. The WHILE loop tests a condition before each pass and may run zero times. The numeric FOR loop counts through a fixed low-to-high range. On top of those, the cursor FOR loop iterates the rows of a query.

Pick the basic LOOP when you must run at least once and exit mid-body, WHILE when the condition should be checked first, and the numeric FOR loop when you know the range up front.

sql
BEGIN
  FOR i IN 1 .. 3 LOOP
    DBMS_OUTPUT.PUT_LINE('Numeric FOR: ' || i);
  END LOOP;

  FOR i IN REVERSE 1 .. 3 LOOP
    DBMS_OUTPUT.PUT_LINE('Reverse: ' || i);
  END LOOP;
END;
/

Q14. How does SELECT INTO work and what can go wrong?

SELECT INTO runs a query and stores its single-row result directly in one or more variables, or in a %ROWTYPE record. It's the standard way to fetch exactly one row without the ceremony of declaring, opening, and closing an explicit cursor, so it reads cleanly for primary-key lookups.

Two things go wrong often. If the query returns no rows it raises NO_DATA_FOUND; if it returns more than one row it raises TOO_MANY_ROWS. A reliable SELECT INTO either targets a primary key or handles both exceptions.

sql
DECLARE
  v_name employees.first_name%TYPE;
BEGIN
  SELECT first_name INTO v_name FROM employees WHERE emp_id = 101;
  DBMS_OUTPUT.PUT_LINE(v_name);
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('No employee 101');
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('More than one match');
END;
/

Key point: NO_DATA_FOUND and TOO_MANY_ROWS is useful because signals you've actually hit these in real code.

Q15. How does exception handling work in PL/SQL?

When a statement in the execution section raises an error, control immediately jumps to the EXCEPTION section, where WHEN clauses match the raised exception by name and run their handler code. The first matching handler wins, and WHEN OTHERS at the end catches anything not named above it, so nothing escapes unhandled if you want a catch-all.

Exceptions come in three flavors: predefined ones with names (NO_DATA_FOUND, ZERO_DIVIDE), non-predefined Oracle errors you bind to a name with PRAGMA EXCEPTION_INIT, and user-defined exceptions you declare and RAISE yourself.

sql
DECLARE
  v_result NUMBER;
BEGIN
  v_result := 10 / 0;
EXCEPTION
  WHEN ZERO_DIVIDE THEN
    DBMS_OUTPUT.PUT_LINE('Cannot divide by zero');
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END;
/

Key point: A bare WHEN OTHERS that swallows the error silently is a red flag. Say you'd at least log SQLCODE and SQLERRM before deciding to continue.

Watch a deeper explanation

Video: PL/SQL tutorial 46: Introduction to PL/SQL exception Handling in Oracle Database By Manish Sharma (Manish Sharma, YouTube)

Q16. The some predefined PL/SQL exceptions and when they fire.

The ones the question expects: NO_DATA_FOUND (a SELECT INTO found no row), TOO_MANY_ROWS (it found more than one), ZERO_DIVIDE (division by zero), INVALID_CURSOR (an illegal cursor operation like fetching from a closed cursor), VALUE_ERROR (a conversion or size error), and DUP_VAL_ON_INDEX (an insert violated a unique constraint).

These have names, so you catch them by name rather than checking error numbers. That readability is the point of predefined exceptions.

ExceptionFires when
NO_DATA_FOUNDSELECT INTO returns zero rows
TOO_MANY_ROWSSELECT INTO returns multiple rows
ZERO_DIVIDEYou divide by zero
DUP_VAL_ON_INDEXAn insert breaks a unique constraint
VALUE_ERRORA conversion or size limit is violated

Q17. What is DBMS_OUTPUT.PUT_LINE and how do you see its output?

DBMS_OUTPUT is a built-in package that writes text to a per-session buffer, mainly for debugging and simple status messages while you develop a block. PUT_LINE appends one line to that buffer, and the client reads the buffer back after the block finishes, so the messages appear together at the end rather than streaming live.

The output isn't shown automatically. In SQL*Plus or SQLcl you run SET SERVEROUTPUT ON first; in tools like SQL Developer you enable the DBMS Output panel. Forget that step and your PUT_LINE calls seem to do nothing, which trips up beginners constantly.

sql
SET SERVEROUTPUT ON;
BEGIN
  DBMS_OUTPUT.PUT_LINE('Hello from PL/SQL');
END;
/

Key point: SET SERVEROUTPUT ON matters.

Q18. How do you write comments in PL/SQL?

Two styles. A double hyphen starts a single-line comment that runs to the end of the line, good for short notes beside a statement. A slash-star to star-slash pair wraps a block comment that can span many lines, good for a header explaining what a procedure does. Both are ignored by the compiler.

Block comments don't nest, so wrapping a section that already contains one closes early. Keep comments explaining why, not restating what the code obviously does.

sql
BEGIN
  -- single-line comment
  NULL;
  /* a block comment
     across two lines */
END;
/

Q19. What do COMMIT, ROLLBACK, and SAVEPOINT do?

COMMIT makes all changes since the last commit permanent and visible to other sessions. ROLLBACK undoes uncommitted changes back to the last commit or to a named savepoint. SAVEPOINT marks a point you can roll back to without discarding the whole transaction.

In PL/SQL you usually let the caller control the transaction rather than committing inside a procedure, because a commit buried in a subprogram can break a larger unit of work the caller is coordinating.

sql
BEGIN
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
  SAVEPOINT after_debit;
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;
  -- if the credit fails, undo just it:
  -- ROLLBACK TO after_debit;
  COMMIT;
END;
/

Key point: Saying you avoid committing inside reusable procedures indicates production maturity. The caller owns the transaction boundary.

Q20. What is the difference between DELETE, TRUNCATE, and DROP?

DELETE is DML: it removes rows one at a time, fires row triggers, respects a WHERE clause, and can be rolled back. TRUNCATE is DDL: it empties the whole table fast by deallocating storage, ignores triggers and WHERE, and commits implicitly so you can't roll it back. DROP is DDL too, but it removes the entire table, its data, and its structure.

In PL/SQL, DELETE runs as a normal statement, but TRUNCATE and DROP are DDL, so you run them through EXECUTE IMMEDIATE. Pick DELETE when you need conditions or rollback, TRUNCATE to clear a table quickly, DROP to remove the object.

DELETETRUNCATEDROP
TypeDMLDDLDDL
WHERE clauseYesNoNo
RollbackYesNo (auto-commit)No (auto-commit)
Removes structureNoNoYes

Key point: The rollback difference is the point interviewers probe: TRUNCATE and DROP commit implicitly, so there's no undo.

Back to question list

PL/SQL Intermediate Interview Questions

Intermediate20 questions

For candidates with working experience: cursors in depth, packages, triggers, and the judgment that separates users from understanders.

Q21. What is a package and why use one?

A package is a schema object that groups related procedures, functions, types, cursors, and variables under one name, split into two parts. The specification declares the public interface that callers can see, and the body holds the implementation plus any private objects. You call the parts through the package name, like payroll.annual_salary.

Packages give you encapsulation (hide the body, expose the spec), a namespace, and a performance edge: the whole package loads into memory on first call, and you can change the body without invalidating code that depends only on the spec.

sql
CREATE OR REPLACE PACKAGE payroll AS
  FUNCTION annual_salary(p_monthly NUMBER) RETURN NUMBER;
END payroll;
/

CREATE OR REPLACE PACKAGE BODY payroll AS
  FUNCTION annual_salary(p_monthly NUMBER) RETURN NUMBER IS
  BEGIN
    RETURN p_monthly * 12;
  END;
END payroll;
/

Key point: The killer detail: changing the body doesn't invalidate callers that depend only on the spec. That reduced recompilation is a real reason teams package everything.

Q22. What is the difference between a package specification and body?

The specification is the public contract: it declares procedure and function headers, types, constants, and public variables that callers are allowed to see and use. The body contains the actual code for those subprograms plus any private declarations, like helper procedures or internal variables, that callers can't reach at all. The two are compiled separately.

A subprogram declared in the spec must be defined in the body. Anything defined only in the body is private to the package. You can compile a spec alone, which is what lets dependent code compile before the body exists.

SpecificationBody
HoldsPublic declarationsImplementation plus private objects
VisibilitySeen by callersHidden from callers
RequiredYesOnly if the spec declares subprograms
Change impactInvalidates dependentsDoes not invalidate spec dependents

Q23. What is a database trigger and what are the main types?

A trigger is a named PL/SQL block that runs automatically when a defined event happens. The common type is a DML trigger firing on INSERT, UPDATE, or DELETE, but there are also DDL triggers (on CREATE, ALTER) and database event triggers (on LOGON, STARTUP).

DML triggers vary along two axes: timing (BEFORE or AFTER the statement) and granularity (statement-level, firing once, or row-level with FOR EACH ROW, firing per affected row). Row-level triggers get :OLD and :NEW to read the pre- and post-change values.

sql
CREATE OR REPLACE TRIGGER trg_audit_sal
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
  IF :NEW.salary < :OLD.salary THEN
    RAISE_APPLICATION_ERROR(-20001, 'Salary cannot decrease');
  END IF;
END;
/

Key point: Explaining BEFORE vs AFTER and statement vs row on the same answer is the bar. The follow-up is usually the mutating table error.

Q24. What are :OLD and :NEW in a trigger?

:OLD and :NEW are pseudo-records available inside a row-level trigger that hold the column values before and after the change. On INSERT, :OLD is null and :NEW is the incoming row; on DELETE, :NEW is null and :OLD is the row being removed; on UPDATE both are populated.

In a BEFORE trigger you can assign to :NEW to change what gets written, which is how you default or normalize values. In an AFTER trigger the row is already set, so :NEW is read-only.

sql
CREATE OR REPLACE TRIGGER trg_upper_email
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
  :NEW.email := LOWER(:NEW.email);
END;
/

Q25. What is the difference between a statement-level and a row-level trigger?

A statement-level trigger fires once per triggering statement, no matter how many rows it touches, and has no access to :OLD or :NEW. A row-level trigger (FOR EACH ROW) fires once for every affected row and can read and, in BEFORE triggers, change :OLD and :NEW.

Use statement-level for logging that a change happened or enforcing table-wide rules; use row-level when the logic depends on each row's values.

Statement-levelRow-level
FiresOnce per statementOnce per affected row
Access to :OLD/:NEWNoYes
ClauseDefaultFOR EACH ROW
Good forAudit that a change ranPer-row validation or defaults

Q26. What is a mutating table error and how do you avoid it?

A mutating table error (ORA-04091) happens when a row-level trigger tries to read or change the very table that fired it, while that table is only partway through the change. Oracle blocks it because the table is in an inconsistent, half-updated state, so any query against it could see wrong or nondeterministic results depending on row order.

The modern fix is a compound trigger, which lets you collect affected keys during the row phase and act on the table in the after-statement phase, when the table is stable. Older code used a package with an array of ids across two triggers. Often the cleanest fix is a constraint or a redesign that doesn't query the mutating table at all.

Key point: Reaching straight for a compound trigger, and mentioning that a constraint may be the real fix, indicates someone who's debugged this in production.

Q27. Can a cursor take parameters, and why is that useful?

Yes. You declare a cursor with a parameter list, then pass values when you OPEN it (or when you name it in a cursor FOR loop). The same cursor definition runs with different inputs each time, so you avoid declaring several near-duplicate cursors that differ only in a WHERE value.

Parameterized cursors keep queries readable and let you reuse one cursor inside a loop that opens it repeatedly with changing arguments, which is cleaner than concatenating values into the query text.

sql
DECLARE
  CURSOR c_by_dept(p_dept NUMBER) IS
    SELECT first_name FROM employees WHERE dept_id = p_dept;
BEGIN
  FOR emp IN c_by_dept(10) LOOP
    DBMS_OUTPUT.PUT_LINE(emp.first_name);
  END LOOP;
END;
/

Q28. What do FOR UPDATE and WHERE CURRENT OF do?

FOR UPDATE in a cursor's query locks the selected rows so no other session can change them while you process the cursor. WHERE CURRENT OF cursor_name then targets an UPDATE or DELETE at exactly the row the cursor last fetched, without repeating the key in a WHERE clause.

Together they give a safe read-then-modify loop: lock the rows, walk them, and change each one in place. The lock releases on commit or rollback.

sql
DECLARE
  CURSOR c_low IS
    SELECT salary FROM employees WHERE salary < 3000 FOR UPDATE;
BEGIN
  FOR emp IN c_low LOOP
    UPDATE employees SET salary = salary * 1.1
    WHERE CURRENT OF c_low;
  END LOOP;
  COMMIT;
END;
/

Key point: Knowing WHERE CURRENT OF requires FOR UPDATE on the cursor is the detail interviewers probe here.

Q29. What is BULK COLLECT and why does it speed things up?

BULK COLLECT fetches many rows from a query into a collection in a single operation instead of one row per fetch. It cuts the number of round trips between the PL/SQL engine and the SQL engine, and those context switches are a big part of row-by-row cost.

For large result sets, fetch in batches with the LIMIT clause so you don't load millions of rows into memory at once. Unbounded BULK COLLECT on a huge table can exhaust process memory.

sql
DECLARE
  TYPE name_tab IS TABLE OF employees.first_name%TYPE;
  v_names name_tab;
  CURSOR c IS SELECT first_name FROM employees;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO v_names LIMIT 500;
    EXIT WHEN v_names.COUNT = 0;
    -- process this batch of up to 500
  END LOOP;
  CLOSE c;
END;
/

Key point: Mentioning LIMIT in practice is the difference between textbook knowledge and having watched a job run out of memory.

Q30. What is FORALL and how does it differ from a FOR loop?

FORALL sends a batch of DML statements (INSERT, UPDATE, or DELETE) to the SQL engine in one call, driven by a collection. A normal FOR loop running the same DML switches to the SQL engine on every iteration; FORALL does it once for the whole batch.

It's not a loop that runs PL/SQL between statements: it's a single bulk DML instruction. Pair it with BULK COLLECT to read and write in batches, and use SAVE EXCEPTIONS to keep going when some rows fail.

sql
DECLARE
  TYPE id_tab IS TABLE OF NUMBER;
  v_ids id_tab := id_tab(101, 102, 103);
BEGIN
  FORALL i IN 1 .. v_ids.COUNT
    UPDATE employees SET salary = salary + 500
    WHERE emp_id = v_ids(i);
  COMMIT;
END;
/

Key point: The trap is calling FORALL a loop. It's a single bulk DML call, and saying so precisely is the signal.

Q31. What are the collection types in PL/SQL?

Three. Associative arrays, also called index-by tables, are keyed by an integer or a string and live only in PL/SQL memory, never in a table. Nested tables are unbounded, can be sparse after deletes, and can be stored in a column. VARRAYs have a fixed maximum size, stay dense, keep element order, and can also be stored in a column.

Reach for an associative array as a fast in-memory lookup or for BULK COLLECT targets. Nested tables and VARRAYs matter when you need to persist a collection in a table column.

Associative arrayNested tableVARRAY
Index typeInteger or stringIntegerInteger
SizeUnboundedUnboundedFixed maximum
Stored in a columnNoYesYes
Sparse allowedYesYes (after deletes)No

Q32. What is a PL/SQL record and how is it different from a collection?

A record is a composite variable that groups several fields, each with its own type, under one name, so it models a single row: you reach fields as v_emp.first_name or v_emp.salary. You can define a record type yourself with TYPE ... IS RECORD, or derive one automatically from a table or cursor with %ROWTYPE.

A record holds one item made of several fields; a collection holds many items of the same type. Combine them (a nested table of records) to hold many rows in memory.

sql
DECLARE
  TYPE emp_rec IS RECORD (
    name VARCHAR2(50),
    sal  NUMBER
  );
  v_emp emp_rec;
BEGIN
  v_emp.name := 'Asha';
  v_emp.sal  := 5000;
  DBMS_OUTPUT.PUT_LINE(v_emp.name || ': ' || v_emp.sal);
END;
/

Q33. What is a REF CURSOR and when do you use it?

A REF CURSOR is a cursor variable: a pointer to a result set that you can assign, pass between subprograms, and return to a client. Unlike a static cursor tied to one query, a ref cursor can point at different queries at runtime.

Use it to return a query result to an application or another procedure, or when the exact query is decided at runtime. SYS_REFCURSOR is the built-in weakly-typed version, so you don't have to declare your own type.

sql
CREATE OR REPLACE PROCEDURE get_emps(p_dept NUMBER, p_cur OUT SYS_REFCURSOR) IS
BEGIN
  OPEN p_cur FOR
    SELECT first_name, salary FROM employees WHERE dept_id = p_dept;
END;
/

Key point: The distinction to state: a normal cursor is bound to one query, a ref cursor is a variable you can point at different queries and hand back to a caller.

Watch a deeper explanation

Video: PL/SQL tutorial 67: PL/SQL Ref Cursors In Oracle Database by Manish Sharma (Manish Sharma, YouTube)

Q34. What are IN, OUT, and IN OUT parameter modes?

IN passes a value into a subprogram and is read-only inside it, and it's the default mode when you write none. OUT sends a value back to the caller; it starts null inside the subprogram and the caller's variable receives whatever you assign to it. IN OUT does both, passing a value in and returning a possibly changed value out.

Use IN for inputs, OUT for results a procedure produces, and IN OUT when the subprogram both reads and updates a caller's variable, like a running total it adjusts.

ModeDirectionRead insideWrite inside
INCaller to subprogramYesNo
OUTSubprogram to callerStarts nullYes
IN OUTBothYesYes

Q35. What does RAISE_APPLICATION_ERROR do?

RAISE_APPLICATION_ERROR raises a custom error with a number you choose in the range -20000 to -20999 and a message text of your own. The calling application receives it like any Oracle error, so business-rule failures surface cleanly instead of as raw ORA codes.

It's how you turn 'salary can't go negative' into a real, catchable exception with a readable message. Pair it with a user-defined exception when you want callers to catch that specific condition by name.

sql
BEGIN
  IF :NEW.salary < 0 THEN
    RAISE_APPLICATION_ERROR(-20010, 'Salary must be non-negative');
  END IF;
END;
/

Q36. How do you declare and raise a user-defined exception?

Declare an exception in the DECLARE section as name EXCEPTION, then trigger it with RAISE name where the condition fails, and catch it in a WHEN name THEN handler. The exception carries no number by default; it's a named flag for a business condition.

For a condition tied to a specific Oracle error number, bind it with PRAGMA EXCEPTION_INIT so a raw ORA error maps to a name you can catch.

sql
DECLARE
  insufficient_funds EXCEPTION;
  v_balance NUMBER := 50;
BEGIN
  IF v_balance < 100 THEN
    RAISE insufficient_funds;
  END IF;
EXCEPTION
  WHEN insufficient_funds THEN
    DBMS_OUTPUT.PUT_LINE('Balance too low');
END;
/

Q37. What do SQLCODE and SQLERRM return?

Inside an exception handler, SQLCODE returns the numeric error code of the exception: 0 when there's no error, a negative ORA number for most errors, and +100 for no-data-found in some contexts. SQLERRM returns the matching human-readable error message text for that code, which is what you'd write to a log to explain what actually went wrong.

Log both when you catch WHEN OTHERS so a swallowed error still leaves a trace. Reraising after logging, or using DBMS_UTILITY.FORMAT_ERROR_BACKTRACE for the line number, keeps you from hiding the real problem.

sql
BEGIN
  INSERT INTO employees(emp_id) VALUES (101);
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('Code: ' || SQLCODE);
    DBMS_OUTPUT.PUT_LINE('Msg:  ' || SQLERRM);
END;
/

Q38. What is dynamic SQL and how does EXECUTE IMMEDIATE work?

Dynamic SQL is SQL built as a string and run at runtime, for cases where the statement isn't known at compile time: a table name from a variable, DDL like CREATE, or a query assembled from conditions. EXECUTE IMMEDIATE parses and runs that string.

Bind values with USING placeholders rather than concatenating them into the text. Binds are faster (the statement is reused) and safe against SQL injection, which raw concatenation is not.

sql
DECLARE
  v_id  NUMBER := 101;
  v_sal NUMBER;
BEGIN
  EXECUTE IMMEDIATE
    'SELECT salary FROM employees WHERE emp_id = :id'
    INTO v_sal USING v_id;
  DBMS_OUTPUT.PUT_LINE(v_sal);
END;
/

Key point: The follow-up is always about injection. Say you bind with USING and never concatenate user input into the statement text.

Q39. What is an autonomous transaction?

An autonomous transaction is a child transaction that commits or rolls back on its own, fully independent of the main transaction that called it. You mark a subprogram with PRAGMA AUTONOMOUS_TRANSACTION, and from then on its own COMMIT persists even if the caller later rolls everything else back, because the two transactions no longer share a fate.

The classic use is logging: you want an audit or error record to survive even when the main work is rolled back. Overuse causes confusion, so reserve it for genuinely independent side effects.

sql
CREATE OR REPLACE PROCEDURE log_event(p_msg VARCHAR2) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO event_log(msg, logged_at) VALUES (p_msg, SYSTIMESTAMP);
  COMMIT;  -- commits independently of the caller
END;
/

Q40. When would you use a view versus a stored procedure?

A view is a stored query you select from as if it were a table: it packages a SELECT for reuse, hides join complexity, and can restrict which columns and rows a user sees, but it holds no procedural logic. A stored procedure holds the logic instead: loops, conditions, DML, and error handling that a view simply can't express.

Use a view to simplify or secure a read; use a procedure when you need to do something with steps, branching, or transaction control. They're complementary, not competing.

ViewStored procedure
ContainsA stored SELECTProcedural logic
Used in a queryYes, like a tableNo
Can run DML with logicNoYes
Best forReusable reads, securityMulti-step operations
Back to question list

PL/SQL Interview Questions for Experienced Developers

Experienced20 questions

advanced rounds probe performance, internals, and production scars. Expect every answer here to draw a follow-up.

Q41. What is the SQL-to-PL/SQL context switch and why does it matter for performance?

PL/SQL code runs in the PL/SQL engine, but every SQL statement is handed to the SQL engine to execute. Each hand-off is a context switch with real overhead. A loop that runs one SQL statement per row pays that cost on every iteration.

This is why bulk processing wins. BULK COLLECT reads many rows in one switch and FORALL writes many in one switch, so a job that did ten thousand switches drops to a handful. On large volumes the difference is minutes versus seconds.

Row-by-row loop vs BULK COLLECT + FORALL: context switches

SQL-to-PL/SQL context switches to process N = 10,000 rows. Row-by-row switches once per row; bulk processes in one operation per direction.

Row-by-row FOR loop
10,000 context switches
BULK COLLECT + FORALL
2 context switches
  • Row-by-row FOR loop: One switch per row: 10,000 round trips
  • BULK COLLECT + FORALL: One switch to read, one to write

Key point: This concept underlies half the tuning questions. If you can explain it clearly, BULK COLLECT and FORALL stop being memorized tricks.

Q42. How do you find and fix a slow PL/SQL program?

Measure first. Use DBMS_PROFILER or the hierarchical DBMS_HPROF to see where time goes, and check EXPLAIN PLAN and SQL trace for the SQL inside the code, because the slow part is usually a query, not the procedural logic. Guessing at bottlenecks is usually wrong.

Then fix in order of impact: replace row-by-row loops with BULK COLLECT and FORALL, fix bad SQL and missing indexes, avoid unnecessary commits inside loops, and cut redundant SQL-to-PL/SQL switches. Rewriting logic as a single set-based SQL statement often beats any PL/SQL tuning.

  • Profile with DBMS_PROFILER or DBMS_HPROF, don't guess
  • Check the SQL with EXPLAIN PLAN and SQL trace
  • Replace row loops with BULK COLLECT and FORALL
  • Prefer one set-based SQL statement over a procedural loop when possible

Key point: the check is for the discipline (measure, don't guess) and that you know pure set-based SQL often beats any PL/SQL.

Q43. What is a compound trigger and what problem does it solve?

A compound trigger is a single trigger with up to four timing sections in one body: BEFORE STATEMENT, BEFORE EACH ROW, AFTER EACH ROW, and AFTER STATEMENT, sharing a common state area. It replaces the old pattern of coordinating several separate triggers through a package.

Its main job is dodging the mutating table error: collect affected keys in the row sections into a collection, then act on the table in AFTER STATEMENT, when the table is stable. One object, shared state, no package juggling.

sql
CREATE OR REPLACE TRIGGER trg_comp
FOR UPDATE OF salary ON employees
COMPOUND TRIGGER
  TYPE id_tab IS TABLE OF employees.emp_id%TYPE;
  g_ids id_tab := id_tab();

  AFTER EACH ROW IS
  BEGIN
    g_ids.EXTEND;
    g_ids(g_ids.COUNT) := :NEW.emp_id;
  END AFTER EACH ROW;

  AFTER STATEMENT IS
  BEGIN
    -- safe to query employees here
    NULL;
  END AFTER STATEMENT;
END trg_comp;
/

Key point: Naming the four timing sections and connecting them to the mutating table fix is the senior-level answer here.

Q44. What is the difference between definer's rights and invoker's rights?

By default a stored subprogram runs with definer's rights: it uses the privileges and schema of the user who created it, so every caller accesses the same owner's objects. Marking it AUTHID CURRENT_USER makes it run with invoker's rights, using the caller's privileges and name resolution.

Definer's rights centralize access (callers don't need direct table grants). Invoker's rights suit code that should act on each caller's own schema objects, like a shared utility that operates on whatever tables the current user owns.

Definer's rightsInvoker's rights
DefaultYesNo (AUTHID CURRENT_USER)
Runs asThe ownerThe caller
Object resolutionOwner's schemaCaller's schema
Good forControlled central accessShared code on caller's objects

Q45. What does PRAGMA EXCEPTION_INIT do?

PRAGMA EXCEPTION_INIT binds a named exception you declare to a specific Oracle error number. After the binding, you can catch that raw ORA error by a readable name in a WHEN clause, instead of falling into WHEN OTHERS and testing SQLCODE by hand. The code indicates intent (WHEN child_record_exists) rather than as a magic negative number.

It's how you handle non-predefined Oracle errors gracefully. For example, bind ORA-02292 (child record found on delete) to a name like child_exists and catch it directly.

sql
DECLARE
  child_record_exists EXCEPTION;
  PRAGMA EXCEPTION_INIT(child_record_exists, -2292);
BEGIN
  DELETE FROM departments WHERE dept_id = 10;
EXCEPTION
  WHEN child_record_exists THEN
    DBMS_OUTPUT.PUT_LINE('Department still has employees');
END;
/

Q46. What is native compilation and how does it differ from interpreted PL/SQL?

By default PL/SQL compiles to bytecode (MCODE) that the PL/SQL runtime interprets at execution time. Native compilation instead translates the code into machine code the CPU runs directly, controlled by the PLSQL_CODE_TYPE setting, which you set to INTERPRETED or NATIVE per session or for the whole database.

Native compilation speeds up procedural-heavy code (loops, arithmetic, computation) but does little for code dominated by SQL, since the SQL executes the same way either way. So it helps compute-bound packages more than data-bound ones.

Q47. What is the PL/SQL function result cache?

Marking a function RESULT_CACHE stores its return value keyed by its arguments in a shared, server-side cache that all sessions can read. Later calls with the same arguments return the cached result without re-running the body, and Oracle automatically invalidates the affected entries whenever a table the function depends on changes, so callers never see a stale value.

It fits pure, read-mostly functions whose inputs repeat: reference lookups, expensive derivations over slow-changing tables. It's the wrong tool for functions with side effects or fast-changing data, where the invalidation churn cancels the benefit.

sql
CREATE OR REPLACE FUNCTION dept_name(p_id NUMBER)
RETURN VARCHAR2
RESULT_CACHE IS
  v_name departments.name%TYPE;
BEGIN
  SELECT name INTO v_name FROM departments WHERE dept_id = p_id;
  RETURN v_name;
END;
/

Q48. What is a pipelined table function?

A pipelined function returns rows one at a time as they're produced, using PIPE ROW, instead of building the whole collection first and returning it at the end. Callers query it with the TABLE() operator as if it were a table, and rows stream to the consumer immediately.

It suits transforming or generating large row sets inside a query pipeline without buffering everything in memory, and it plays well with parallel query. The consumer starts getting rows before the function finishes.

sql
CREATE OR REPLACE FUNCTION gen_nums(p_n NUMBER)
RETURN sys.odcinumberlist PIPELINED IS
BEGIN
  FOR i IN 1 .. p_n LOOP
    PIPE ROW(i);
  END LOOP;
  RETURN;
END;
/

-- SELECT * FROM TABLE(gen_nums(5));

Q49. What does the NOCOPY hint do on a parameter?

For OUT and IN OUT parameters, PL/SQL normally passes by value: it copies the argument in, works on the copy, and copies it back on a clean return. NOCOPY asks the compiler to pass by reference instead, skipping the copies.

It matters for large collections or records, where copying is expensive. The catch: with NOCOPY, a partial change followed by an unhandled exception can leave the caller's variable half-modified, because there was no safe copy to discard. It's a hint, so the compiler may ignore it.

Key point: The trade-off is the whole answer: NOCOPY saves copying big parameters but loses the rollback-on-exception safety of pass-by-value.

Q50. Why do bind variables matter for performance and security?

A bind variable is a placeholder whose value is supplied at execution, so the SQL text stays identical across calls. Oracle parses and plans a statement once and reuses that plan for every value, instead of hard-parsing a new statement for each literal.

That reuse cuts parsing overhead and library cache contention on high-frequency statements. Binds also stop SQL injection, because the value can never change the statement's structure. Concatenating literals into dynamic SQL breaks both benefits.

sql
-- reuses one parsed statement, injection-safe
EXECUTE IMMEDIATE
  'UPDATE employees SET salary = :s WHERE emp_id = :id'
  USING v_new_sal, v_id;

Key point: Connecting binds to both plan reuse and injection safety in one answer is what a senior interviewer wants to hear.

Q51. How does FORALL with SAVE EXCEPTIONS handle partial failures?

Normally a FORALL stops at the very first element that raises an error and rolls back the whole bulk statement. Adding the SAVE EXCEPTIONS clause changes that: FORALL keeps processing every remaining element, quietly collects each failure as it goes, and then raises a single ORA-24381 at the end to tell you some rows failed.

You read the collected errors from the SQL%BULK_EXCEPTIONS collection, which holds the index and error code of each failed element. That lets a batch load skip and log bad rows instead of aborting on the first one.

sql
BEGIN
  FORALL i IN 1 .. v_ids.COUNT SAVE EXCEPTIONS
    INSERT INTO target VALUES (v_ids(i));
EXCEPTION
  WHEN OTHERS THEN
    FOR j IN 1 .. SQL%BULK_EXCEPTIONS.COUNT LOOP
      DBMS_OUTPUT.PUT_LINE('Row ' || SQL%BULK_EXCEPTIONS(j).ERROR_INDEX ||
        ' failed: ' || SQL%BULK_EXCEPTIONS(j).ERROR_CODE);
    END LOOP;
END;
/

Q52. What does the DETERMINISTIC keyword mean on a function?

DETERMINISTIC tells Oracle a function always returns the same output for the same inputs, with no dependence on session state, time, or table data that changes. That promise lets Oracle cache and reuse results in some contexts, like function-based indexes and materialized views.

It's a contract you make, not something Oracle verifies. Mark a function DETERMINISTIC when it truly is pure; do it wrongly on a function that reads changing data and you get stale, incorrect results from cached values.

Q53. What is package state and how can it cause problems?

Variables declared at package level (in the spec or body, outside any subprogram) hold their values for the life of a session, not just one call. That persistent package state can act as a session cache, but it also carries surprises.

Two bite in production: changing a package's spec or body invalidates that state and raises ORA-04068 on the next call in existing sessions, and stale cached values persist across unrelated calls in the same session. Pragma SERIALLY_REUSABLE resets state per call for stateless designs.

Key point: Mentioning ORA-04068 after a recompile, and that connection pooling makes session-level state risky, indicates real operational experience.

Q54. Why is committing inside a row-by-row loop usually a bad idea?

Frequent commits inside a loop add overhead (each commit forces a redo log flush) and, worse, break restartability: if the job fails halfway, some rows are committed and some aren't, so a rerun double-processes or skips work. It can also trigger ORA-01555 snapshot-too-old on long cursors.

The better pattern is one commit per logical unit of work, often the whole batch, or periodic commits every N thousand rows only when a single transaction is genuinely too large. Bulk processing usually removes the temptation entirely.

Key point: The restartability point matters more than the performance point. Saying 'partial commits make failed jobs hard to rerun safely' is The production-ready answer.

Q55. What collection methods should you know, and what is a sparse collection?

The methods to know: COUNT gives the number of elements, FIRST and LAST give the lowest and highest indexes, NEXT and PRIOR handle from one index to the next, EXISTS checks whether an index is present, EXTEND grows a nested table or VARRAY, and DELETE and TRIM remove elements. They turn a raw collection into something you can safely iterate.

A collection is sparse when its indexes have gaps, usually after DELETE. That's why you loop with FIRST/NEXT rather than 1..COUNT on a possibly-sparse collection: a plain numeric loop hits a deleted index and raises NO_DATA_FOUND.

sql
DECLARE
  TYPE num_tab IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  v num_tab;
  i PLS_INTEGER;
BEGIN
  v(1) := 10; v(5) := 50; v(9) := 90;  -- sparse
  i := v.FIRST;
  WHILE i IS NOT NULL LOOP
    DBMS_OUTPUT.PUT_LINE(i || ' => ' || v(i));
    i := v.NEXT(i);
  END LOOP;
END;
/

Key point: Explaining why FIRST/NEXT beats 1..COUNT on a sparse collection is the trap-aware answer the technical value is.

Q56. When should you prefer static SQL over dynamic SQL?

Prefer static SQL whenever the statement is known at compile time. Static SQL is checked at compile time (invalid columns fail early), records dependencies so recompilation is automatic, and is easier to read and tune. Dynamic SQL loses all of that.

Use dynamic SQL only when you genuinely can't write the statement statically: unknown table or column names, DDL, or optional filters assembled at runtime. Even then, bind values with USING rather than concatenating them, for plan reuse and injection safety.

Static SQLDynamic SQL
Checked at compile timeYesNo, at runtime
Dependency trackingAutomaticNone
Injection riskNoneIf you concatenate input
Use whenStatement is knownStatement built at runtime

Q57. What is subprogram overloading and where is it allowed?

Overloading means declaring several subprograms with the same name but different parameter lists, so callers use one name and Oracle picks the version that matches the arguments. It's allowed inside a package or a block, not for standalone stored procedures and functions at schema level.

The versions must differ in the number, type, or family of parameters; you can't overload on return type alone or on parameter names. Overloading built-in-style helpers (a print that accepts a NUMBER or a VARCHAR2) keeps the caller-facing API small.

sql
CREATE OR REPLACE PACKAGE fmt AS
  FUNCTION show(p IN NUMBER)   RETURN VARCHAR2;
  FUNCTION show(p IN DATE)     RETURN VARCHAR2;
END fmt;
/

Key point: Naming the restriction (no overloading on return type, and not for schema-level standalone subprograms) is what separates a full answer from a partial one.

Q58. How do you schedule PL/SQL to run on a recurring basis?

The modern tool is DBMS_SCHEDULER, which creates named jobs that run a PL/SQL block, a stored procedure, or an external script on a calendar or interval schedule. It replaces the older, thinner DBMS_JOB package, adding run logging, job classes for prioritization, scheduling windows, and dependency chains that let one job trigger another when it finishes.

You define the job with an action, a start time, and a repeat_interval expressed in a calendaring syntax like FREQ=DAILY. The scheduler then runs it, records success or failure, and retries per your settings, so batch work runs without an external cron.

sql
BEGIN
  DBMS_SCHEDULER.CREATE_JOB(
    job_name        => 'nightly_rollup',
    job_type        => 'PLSQL_BLOCK',
    job_action      => 'BEGIN run_rollup; END;',
    start_date      => SYSTIMESTAMP,
    repeat_interval => 'FREQ=DAILY; BYHOUR=2',
    enabled         => TRUE);
END;
/

Q59. How do you prevent SQL injection in PL/SQL dynamic SQL?

The core rule is simple: never concatenate user input into a SQL string. Pass data values through bind variables with the USING clause of EXECUTE IMMEDIATE, so the input can only ever be treated as a value, never as part of the statement's structure. That single habit closes the door on the most common injection path in dynamic PL/SQL.

When the dynamic part is an identifier (a table or column name that can't be bound), validate it against a known allow-list or pass it through DBMS_ASSERT, which checks that a string is a legal, safe identifier. Prefer static SQL wherever the statement is fixed, since static SQL can't be injected at all.

sql
-- unsafe: input becomes part of the statement
-- EXECUTE IMMEDIATE 'SELECT ... WHERE name = ''' || p_name || '''';

-- safe: input is a bound value
EXECUTE IMMEDIATE
  'SELECT emp_id FROM employees WHERE name = :n'
  INTO v_id USING p_name;

Key point: Bind values, allow-list or DBMS_ASSERT identifiers, prefer static SQL. Hitting all three is the senior-level answer.

Q60. Design question: how would you write a PL/SQL job to process millions of rows efficiently?

Clarify first (source, target, transform, failure tolerance, restartability), then design around bulk processing and set-based work. Read with BULK COLLECT and a LIMIT so memory stays bounded, transform in memory, and write with FORALL plus SAVE EXCEPTIONS so bad rows are logged rather than aborting the run.

Commit per batch, not per row, and track progress (last processed key) so a failed job restarts where it stopped. Push whatever you can into a single set-based SQL statement, because one INSERT SELECT usually beats any procedural loop. The structure, clarify, bulk read, transform, bulk write, restartable commits, is what the question scores.

sql
DECLARE
  CURSOR c IS SELECT * FROM staging;
  TYPE t IS TABLE OF staging%ROWTYPE;
  rows t;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO rows LIMIT 5000;
    EXIT WHEN rows.COUNT = 0;
    FORALL i IN 1 .. rows.COUNT SAVE EXCEPTIONS
      INSERT INTO target VALUES rows(i);
    COMMIT;  -- one commit per batch
  END LOOP;
  CLOSE c;
END;
/

Key point: The The technical sequence is and the restartability thinking more than any single keyword.

Back to question list

PL/SQL vs SQL, T-SQL, and PostgreSQL PL/pgSQL

PL/SQL isn't a replacement for SQL: it wraps SQL in procedural logic so you can loop, branch, and handle errors on the server instead of shuttling every row to an application. Compared with its procedural cousins, PL/SQL is Oracle-specific, T-SQL is Microsoft SQL Server's dialect, and PL/pgSQL is PostgreSQL's. They share the same idea (procedural code stored in the database) but differ in syntax, error handling, and which vendor's engine they run on. Naming these trade-offs out loud is itself an interview signal: it shows you know PL/SQL is one option among several, tied to Oracle.

FeatureSQLPL/SQLT-SQL (SQL Server)PL/pgSQL (PostgreSQL)
NatureDeclarative query languageProcedural extension to SQLProcedural extension to SQLProcedural extension to SQL
VendorANSI standard, all vendorsOracle onlyMicrosoft SQL ServerPostgreSQL
Code unitSingle statementBlock, procedure, packageBatch, stored procedureFunction, DO block
Error handlingNone built inEXCEPTION sectionTRY...CATCHEXCEPTION block
Runs whereDatabase engineOracle DB engineSQL Server enginePostgreSQL engine

How to Prepare for a PL/SQL Interview

Prepare in layers, and practice out loud. Most PL/SQL rounds move from concept questions to writing a procedure or cursor to a debugging or tuning discussion, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts until you can explain them without notes, then read one tier up for the stretch questions.
  • Type and run every snippet in a real schema; modifying working code cements it far faster than reading.
  • Practice thinking aloud on small problems with a timer, because your process, not just the final answer is the technical point.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical PL/SQL interview flow

1Recruiter or phone screen
background, Oracle versions used, a few concept checks
2Technical concepts
blocks, cursors, exceptions, triggers, packages
3Live coding
write a procedure or cursor loop and explain it under observation
4Debugging or tuning
read unfamiliar code, fix an error, discuss bulk processing

Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.

Test Yourself: PL/SQL Quiz

Ready to test your PL/SQL knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which PL/SQL topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Are these questions enough to pass a PL/SQL interview?

They cover the question-answer portion well, but most PL/SQL rounds also include live coding: writing a procedure or cursor loop under observation while explaining your thinking. solving small problems out loud with a timer is the practice path. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

Do I need Oracle Database installed to practice?

It helps a lot. Oracle offers a free Express Edition and a free cloud Autonomous Database you can run queries against, and Oracle Live SQL runs PL/SQL in the browser with no install. Typing and running the snippets on this page in any of those beats reading them, because most PL/SQL bugs only show up when you execute.

How long does it take to prepare for a PL/SQL interview?

If you use PL/SQL at work, one to two weeks of an hour a day covers this bank with practice runs. Starting colder, plan three to four weeks and write blocks daily; reading answers without running them is how preparation quietly fails.

Do interviewers ask exactly these questions?

The concepts repeat far more reliably than the exact wording. Interviewers rephrase, chain follow-ups, and push toward whatever you claim confidence in. Prepare the underlying ideas, cursors, exception handling, packages, bulk processing, and the phrasing takes care of itself.

Is there a way to test my PL/SQL knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It's the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These PL/SQL questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 11 May 2026Last updated: 16 Jul 2026
Share: