227 Php Developer Interview Questions & Answers

116 top • 13 Amazon • 21 Google • 10 Netflix • 7 Meta • 18 NVIDIA • 21 Apple • 21 Microsoft

Php Developer icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: September 3, 2026)

21. What is a PHP session?Language SpecificEasy

Question Details

Define a PHP session as server-side state associated with a client through a session identifier, commonly carried in a cookie. Explain session_start, the $_SESSION store, session persistence, regeneration, expiration, storage handlers, locking, logout, and security risks such as fixation and hijacking. Distinguish sessions from cookies and stateless authentication.

Short Interview Answer (30-60 seconds)

A PHP session lets the server keep state for the same client across multiple requests. PHP normally links the client to that state through a session identifier, commonly stored in a cookie. I call session_start() before using $_SESSION. For login sessions, I also regenerate the identifier after authentication, use secure cookie settings, apply clear expiration rules, and destroy the session correctly during logout.

Detailed Explanation

A session is a way for a web site to remember information about one visitor while that person moves between pages. For example, after a person signs in, the site can remember that person instead of asking for a password on every page. The important point is that the main information is kept by the web site. The visitor's browser normally carries only a small identifier. The web site uses that identifier to find the correct saved information when the visitor sends another request.

Useful Questions to Ask the Interviewer
  1. Should I explain the default PHP session behavior as well as custom session storage?
  2. Should I include security practices for login sessions?
What is a PHP session? diagram
How to Explain It in an Interview

A PHP session is server side state associated with a client through a session identifier. The identifier is commonly carried in a cookie named PHPSESSID, although PHP configuration can change the cookie name and transport behavior.

Calling session_start() starts a new session or resumes an existing one. PHP obtains the session identifier, uses the configured session handler to load the matching data, and makes that data available through the $_SESSION array. Values stored in $_SESSION can therefore persist across separate HTTP requests while the session remains available.

The session data is stored through a session save handler. The default files handler stores session data in files. Applications can configure another supported handler or provide a custom handler when they need a different storage system.

A session is different from a cookie. A cookie stores its own value on the client. With a normal PHP session, the important application state is stored on the server and the client usually carries only the session identifier. A session is also different from stateless authentication. Stateless authentication does not require the server to load stored session state for every authenticated request.

Security is important because a stolen valid session identifier can allow an attacker to use the victim's session. After authentication or another privilege change, the application should normally regenerate the session identifier with session_regenerate_id(). Secure session cookies should normally use Secure on HTTPS sites, HttpOnly, and an appropriate SameSite setting. Enabling strict session identifier handling can also reduce acceptance of uninitialized identifiers.

Expiration needs application care. Cookie lifetime and server side session data lifetime are separate concerns, and automatic cleanup does not provide an exact security timeout. Sensitive applications should enforce their own idle or absolute timeout rules.

During logout, removing values from $_SESSION alone is not enough for a complete logout. The application should clear authentication state, remove the session cookie when appropriate, and destroy the server side session.

Session locking also matters. With the default files handler, PHP normally locks the session data while the session is open. Concurrent requests using the same session can therefore wait for each other. If code no longer needs to change session data, session_write_close() can save and close the session earlier.

Where it is used

PHP sessions are commonly used for signed in user state, shopping carts, short lived form progress, access control information, and other values that must remain available across several requests. In production systems with several PHP workers or several application servers, the deployment must ensure that requests which need a session can reach the required session data. This may require shared session storage or another deployment strategy that keeps session access consistent.

Why Interviewers Ask This

Interviewers ask this to check whether the candidate understands how PHP keeps user state across separate requests. They also want to see whether the candidate understands session identifiers, session storage, expiration, locking, logout behavior, and security risks such as session fixation and session hijacking.

Common interview mistakes

Common mistakes include thinking the complete session is stored in the browser, forgetting to call session_start() before using $_SESSION, treating the session identifier as harmless data, failing to regenerate the identifier after authentication, assuming session cleanup gives an exact security timeout, forgetting that destroying server side session data does not automatically remove the browser cookie, and leaving a session open longer than necessary when session locking delays concurrent requests.

Interview tip

Start by saying that a PHP session keeps server side state across requests and links that state to a client through a session identifier. Then explain session_start(), $_SESSION, storage, persistence, regeneration, expiration, locking, logout, and the difference between sessions, cookies, and stateless authentication.

Interviewer may ask next
What can happen when two requests from the same PHP session run at the same time?

They can block each other when the active session handler uses locking. With the default files handler, session_start() normally locks the session data, so another request for the same session can wait until the first request closes the session or finishes. This matters for pages that send several requests at once. If the first request no longer needs to change session data, calling session_write_close() releases the session earlier and lets another request continue.

When would you choose a PHP session instead of stateless authentication?

I would choose a PHP session when the application benefits from keeping authentication or other client state on the server and can manage the required session storage. Server side sessions make centralized revocation and state changes straightforward because the application controls the stored session. Stateless authentication avoids loading server side session state for every authenticated request, which can simplify some distributed designs. The tradeoff is that sessions require storage, expiration management, and careful protection of the session identifier.

22. Explain PHP session locking and its effect on concurrent requests.Language SpecificHard

Question Details

Describe when the session lock is acquired and released, why requests sharing a session can serialize, session_write_close, read-only access, correctness risks, and measurement.

Short Interview Answer (30-60 seconds)

With PHP's default file based session handler, session_start obtains an exclusive lock for the session before returning its data. Requests using the same session identifier can therefore wait and run one after another. I keep the session open only while reading or changing the required values, then call session_write_close before slow work. For true read only access, I can use read_and_close. Custom handlers may use different locking rules, so I verify their behavior and measure the wait around session_start.

Detailed Explanation

See the Code while reading this explanation.

The main problem is that two actions from the same user may have to wait for each other when both use the same saved login information. One action temporarily reserves that information while it reads or changes it. A second action then waits until the first action finishes with it. This can make a page, an upload, or a background update feel slow even when the computer has free capacity. The practical fix is to finish any needed changes quickly and release the reservation before starting slow work.

Useful Questions to Ask the Interviewer
  1. Are we using PHP's default file based session handler or a custom handler?
  2. Do these requests only read the session, or do they also change it?
  3. Which slow operations happen after the session is opened?
  4. Are the tests sending the same session cookie in parallel requests?
Explain PHP session locking and its effect on concurrent requests. diagram
How to Explain It in an Interview

With PHP's default file based session handler, session_start obtains an exclusive lock for the current session identifier before the session data is made available to the script. The lock prevents two requests from writing the same session file at the same time.

PHP normally writes the session and releases the lock when the active session is closed near the end of the request. session_write_close writes the current session data and releases the lock immediately. session_abort also releases the lock, but it discards changes made during the current session.

Because the lock belongs to one session identifier, two requests carrying the same session cookie can become serialized. A second request may wait inside session_start until the first request closes its session. Requests using different session identifiers do not contend for that same session lock.

The practical pattern is to call session_start, read or update the required session values, and then call session_write_close before database work, remote calls, report creation, file processing, or other slow operations. Changes made to $_SESSION after the close are only local changes in that request and are not automatically saved.

For read only access, session_start can receive the read_and_close option. PHP reads the session and closes it immediately. Later changes to $_SESSION are not saved. With the default file handler, read_and_close may also leave the session file modification time unchanged, which can matter when file cleanup depends on that time.

Closing early reduces lock waiting, but it can introduce read then write races. Two requests may read the same value, close the session, calculate separately, and later overwrite each other. Important counters, balances, inventory, or workflow state should use a database transaction or another atomic storage operation.

To measure the effect, record time immediately before and after session_start. Send parallel requests with the same session cookie, compare them with different session identifiers, and repeat the test after releasing the session early. Custom handlers can use different locking behavior, so their documentation and production measurements must be checked.

Example

The example provides three modes that use the same session identifier. The hold mode keeps the default file based session locked during a five second delay. A second request with the same session cookie waits inside session_start. The close mode updates the counter, stores the value, calls session_write_close, and then performs the delay without holding the session lock. The read mode uses read_and_close when no session change is required. The waitBeforeStart value shows how long session_start took to return.

Code
<?php

declare(strict_types=1);

header('Content-Type: application/json');

$mode = $_GET['mode'] ?? 'hold';
$beforeStart = microtime(true);

if ($mode === 'read') {
    // Read the session and release its lock immediately.
    if (!session_start(['read_and_close' => true])) {
        throw new RuntimeException('Unable to start the session.');
    }

    $afterStart = microtime(true);
    $count = $_SESSION['count'] ?? 0;

    $response = [
        'mode' => 'read',
        'waitBeforeStart' => $afterStart - $beforeStart,
        'count' => $count,
        'message' => 'The session was read and closed immediately.'
    ];
} elseif ($mode === 'close') {
    if (!session_start()) {
        throw new RuntimeException('Unable to start the session.');
    }

    $afterStart = microtime(true);

    // Finish the required session change while the lock is held.
    $_SESSION['count'] = ($_SESSION['count'] ?? 0) + 1;
    $count = $_SESSION['count'];

    // Save the session and release the lock before slow work.
    if (!session_write_close()) {
        throw new RuntimeException('Unable to write and close the session.');
    }

    sleep(5);

    $response = [
        'mode' => 'close',
        'waitBeforeStart' => $afterStart - $beforeStart,
        'count' => $count,
        'message' => 'Slow work ran after the session lock was released.'
    ];
} else {
    if (!session_start()) {
        throw new RuntimeException('Unable to start the session.');
    }

    $afterStart = microtime(true);

    $_SESSION['count'] = ($_SESSION['count'] ?? 0) + 1;
    $count = $_SESSION['count'];

    // The default file based session remains locked during this delay.
    sleep(5);

    $response = [
        'mode' => 'hold',
        'waitBeforeStart' => $afterStart - $beforeStart,
        'count' => $count,
        'message' => 'Slow work ran while the session remained open.'
    ];
}

echo json_encode($response, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
Where it is used

Session locking matters in applications where one browser sends several requests at the same time. Common examples include dashboards with background requests, upload progress checks, report generation, long database operations, payment pages, and pages that call several endpoints. It is especially important with PHP FPM because separate worker processes can still wait for the same stored session. Releasing the session before slow work improves response concurrency, but important shared business state should not rely on an early closed session for atomic updates.

Why Interviewers Ask This

Interviewers ask this question to check whether the candidate understands how PHP session storage can coordinate requests handled by separate workers. They also want to see whether the candidate knows when a lock is obtained, when it is released, how session_write_close and read_and_close affect concurrency, and how early closing can create correctness risks.

Common interview mistakes

A common mistake is assuming that requests from the same user always run independently. Another is calling session_start at the beginning of every script and keeping the session open during slow work. Code that only reads values still holds the default file based lock when it uses a normal session_start call. Developers may call session_write_close and then change $_SESSION, incorrectly expecting those later changes to be saved. They may also use read_and_close and later attempt to persist changes. Other mistakes include ignoring session_start failures, measuring only total response time, testing with different session identifiers, and assuming every custom handler follows the default file handler's locking behavior.

Interview tip

Begin with the practical conclusion that requests sharing one session can serialize. Then state that the default file based handler locks during session_start, explain when the lock is released, show session_write_close as the normal early release method, mention read_and_close for read only access, and finish with custom handler differences and lost update risks.

Interviewer may ask next
What happens if code changes $_SESSION after calling session_write_close?

Those later changes are not automatically saved. session_write_close writes the current session data, ends the active session, and releases its lock at that point. The $_SESSION array can still contain local values in the running script, but changing the array does not reopen the session or persist the new values. This matters because the current request may display the changed value while the next request reads the older stored value. Required session changes should be completed before closing.

When should important shared state be moved out of the PHP session?

Important state should be moved when several requests must update it safely and concurrently. Examples include balances, inventory, counters, and workflow transitions. A database transaction or another atomic storage operation can enforce the required consistency without keeping the PHP session locked during unrelated slow work. The tradeoff is that session locking gives simple serialization for session data, while dedicated storage requires more design but provides clearer concurrency rules and better control over important updates.

23. What are typed properties, union types, intersection types, and nullable types in PHP?Language SpecificMedium

Question Details

Explain initialization rules, coercive versus strict calls, variance constraints, nullability, false or true literal types where applicable, and API-design tradeoffs.

Short Interview Answer (30-60 seconds)

These features let me describe PHP values more precisely. A typed property accepts only its declared type and may remain uninitialized until I assign it. A union accepts any one listed type. An intersection requires an object that satisfies every listed class or interface type. A nullable type accepts the declared type or null. I also remember that strict types affects scalar coercion at the calling or assignment location, while inheritance rules control whether child declarations may become broader or narrower.

Detailed Explanation

See the Code while reading this explanation.

These PHP features let a program clearly state what kind of value is allowed. A class field can require one kind of value. A method can allow one value from several choices. It can also require one object to provide several abilities at the same time. Another form allows either a normal value or no value, represented by null. These rules help PHP detect incorrect values earlier. They also help developers understand how a class or method should be used without reading all of its internal code.

Useful Questions to Ask the Interviewer
  1. Which PHP versions must the application support?
  2. Is null a real business value or only a missing value?
  3. Should the public API accept several representations or one normalized value?
What are typed properties, union types, intersection types, and nullable types in PHP? diagram
How to Explain It in an Interview

A typed property declares the type a class property may contain, such as public int $count. If the property has no default and the constructor does not assign it, it is uninitialized. Reading it then throws an Error. Declaring ?string does not initialize the property to null. Use public ?string $name = null when null should be the initial value.

A union type such as int|string accepts a value matching any one member. PHP first prefers an exact match. When strict types is disabled, compatible scalar values may be coerced. With strict types enabled, incompatible scalar values normally cause a TypeError, although an integer is accepted where a float is declared. Strictness is determined by the file performing the call. For property assignment, it is determined by the code performing the assignment.

An intersection type such as Countable&Iterator accepts only an object that satisfies every listed class or interface type. Pure intersection types were added in PHP 8.1. PHP 8.2 added disjunctive normal form types, so a declaration may use a grouped intersection inside a union, such as (Countable&Iterator)|array. The parentheses are required.

A nullable type means the declared type or null. ?User is equivalent to User|null. The short question mark form cannot be combined with additional union members. For several alternatives, write User|string|null. From PHP 8.4, relying on an implicitly nullable parameter through a null default is deprecated, so null should be declared explicitly.

PHP also supports the standalone literal types false, true, and null from PHP 8.2. Use bool instead of true|false. Literal types can describe legacy return values precisely, but a result object, enum, exception, or nullable return may create a clearer new API.

Method parameters are contravariant, so a child may accept a broader type. Return types are covariant, so a child may return a narrower type. Normal properties that can be both read and written are invariant. PHP 8.4 property hooks can permit covariance for a property that is only read and contravariance for one that is only written.

Type declarations add runtime checks but do not normally create copies of values or allocate replacement objects. Their direct performance and memory cost is usually small. The larger production benefit is earlier failure, clearer contracts, and better static analysis.

Example

The example uses a typed integer property, a nullable string property with an explicit null default, a union parameter, an intersection parameter, and a literal false return type. The constructor initializes the required property before it can be read. The identifier function accepts either an integer or string and returns one normalized string form. The collection function accepts only an object implementing both Countable and Iterator. The lookup function demonstrates a legacy style User|false result, while the surrounding explanation notes that a clearer result design may be preferable for a new API.

Code
<?php

declare(strict_types=1);

final class User
{
    public int $id;
    public ?string $nickname = null;

    public function __construct(int $id)
    {
        // Initialize the required typed property before it is read.
        $this->id = $id;
    }
}

function normalizeId(int|string $id): string
{
    // The union allows either an integer or a string.
    return (string) $id;
}

function countAndRead(Countable&Iterator $items): int
{
    // The object must satisfy both interfaces.
    $items->rewind();

    if ($items->valid()) {
        echo (string) $items->current(), PHP_EOL;
    }

    return count($items);
}

function findUser(int $id): User|false
{
    // The literal false type represents a failed legacy lookup.
    if ($id <= 0) {
        return false;
    }

    return new User($id);
}

$user = findUser(10);

if ($user !== false) {
    echo normalizeId($user->id), PHP_EOL;
    var_dump($user->nickname);
}

$items = new ArrayIterator(['a', 'b', 'c']);
echo countAndRead($items), PHP_EOL;
Where it is used

Typed properties are common in domain models, services, configuration objects, data transfer objects, and framework components. Union types are useful when an API intentionally accepts a small set of representations, such as an identifier that may be an integer or string. Intersection types are useful when one object must provide several capabilities, such as being both countable and iterable. Nullable types are appropriate when absence is a valid and documented state. In production code, narrow and explicit declarations improve testing, refactoring, static analysis, and error detection. Broad unions and unnecessary null values should be avoided because they move complexity into every caller.

Why Interviewers Ask This

Interviewers ask this question to check whether a candidate understands how PHP enforces type contracts at runtime. They also evaluate knowledge of property initialization, scalar coercion, strict calls, inheritance variance, null handling, literal types, version boundaries, and the tradeoffs involved in designing clear public APIs.

Common interview mistakes

A common mistake is assuming that a nullable property automatically starts as null. It remains uninitialized unless it has a null default or receives an assignment. Another mistake is believing strict types disables every conversion everywhere. It mainly controls scalar coercion according to the file performing a call or property assignment, and an integer is still valid for a float declaration. Developers also create unions that are too broad, place scalar types in intersections, omit the required parentheses in a grouped intersection and union, write redundant combinations such as bool|false, or change inherited declarations in an invalid direction. Another mistake is using false and null as interchangeable failure values without documenting their different meanings.

Interview tip

Define each form in one sentence first. Then explain the uninitialized property state, one member versus every member, explicit nullability, coercive and strict behavior, and method variance. Finish with one practical API tradeoff, such as preferring a clear value object over a growing union.

Interviewer may ask next
What happens if code reads a nullable typed property before assigning any value?

It throws an Error because nullable and initialized are separate ideas. A declaration such as public ?string $name permits either a string or null after assignment, but the property initially remains uninitialized. Giving it = null creates an actual initialized null value. This matters because even a type safe property can fail at runtime when construction does not establish a valid initial state.

When should an API use a union type instead of a shared interface or value object?

Use a union when the alternatives are few, intentional, and represent the same clear concept, such as int|string for an external identifier. Prefer a shared interface or value object when each alternative needs different handling, validation, or behavior. A union is simple for callers at first, but a dedicated abstraction can keep the contract clearer and prevent repeated type checks as the API grows.

24. How do exceptions and the Throwable hierarchy work in modern PHP?Language SpecificMedium

Question Details

Compare Exception and Error, catch ordering, finally behavior, rethrowing with a previous exception, domain-specific exceptions, and boundary-level handling.

Short Interview Answer (30-60 seconds)

In modern PHP, every object used with throw must be an instance of a class that implements Throwable. Exception is the base class for user exceptions, while Error is the base class for many engine detected failures such as TypeError. I catch specific types before broad types, use finally only for reliable cleanup, rethrow the same object when no extra meaning is needed, and wrap a failure with a previous Throwable when translating it into a domain specific exception. I normally catch Throwable broadly only at an outer application boundary.

Detailed Explanation

See the Code while reading this explanation.

The practical goal is to handle each failure at the place that can make a useful decision. Modern PHP puts thrown failures into one family so code can catch either a specific problem or every throwable problem. Application code usually creates exceptions for operations that cannot continue. PHP itself can create errors for invalid calls, types, or other engine detected problems. Handler order decides which response runs. Cleanup must happen whether work succeeds or fails. Production code must also preserve the original cause and avoid showing private failure details to users.

Useful Questions to Ask the Interviewer
  1. Is the boundary an HTTP request, command, or queue worker?
  2. Which failures can the application recover from locally?
  3. Which failure details may be shown to the caller?
How do exceptions and the Throwable hierarchy work in modern PHP? diagram
How to Explain It in an Interview

Throwable is the common interface for objects that PHP allows to be thrown. Exception and Error both implement it. Exception is the base class for user exceptions. Error is the base class for many internal PHP errors, including TypeError and ValueError. A user class cannot implement Throwable directly. It must extend Exception or one of its subclasses.

Not every PHP warning or notice becomes a Throwable. Traditional errors still follow PHP error reporting unless code converts a supported error into ErrorException with an error handler. Some failures also happen before the relevant try block can run. For example, a syntax error in the main file cannot be caught by code in that same file.

PHP tests catch blocks from top to bottom and runs the first compatible handler. A domain specific exception must therefore appear before RuntimeException, Exception, or Throwable. PHP also permits one catch block to name several types when they need identical handling.

A finally block runs after try and any matching catch during normal exception handling. It also runs before a pending return completes. A return inside finally replaces an earlier return. If try and finally both throw, the Throwable from finally is propagated and the earlier Throwable is placed in its previous chain. Cleanup in finally should therefore be small and reliable.

Rethrowing with throw $error sends the same object onward and keeps its existing trace. Wrapping creates a new domain exception and passes the original Throwable as the previous constructor argument. Wrapping is useful when a lower level failure needs application meaning.

Catch a failure locally only when code can recover, add meaning, or clean up. Catching Throwable at an outer request, command, or worker boundary is useful for logging and producing a controlled failure result. Creating and throwing a Throwable allocates an object and records diagnostic information, including a trace, so exceptions should represent exceptional paths rather than routine branching.

Example

The example uses OrderProcessingException as a domain specific exception. The storage function throws a lower level RuntimeException. The service catches that specific type and wraps it in OrderProcessingException by passing the original Throwable as the previous argument. The finally block performs reliable cleanup whether the operation succeeds or throws. At the outer boundary, the domain exception is caught before Throwable. The expected domain failure receives a controlled response, while any other Throwable is logged and produces a general failure response. The code does not treat the failed operation as successful.

Code
<?php

declare(strict_types=1);

final class OrderProcessingException extends RuntimeException
{
}

function saveOrder(): void
{
    // Simulate a lower level storage failure.
    throw new RuntimeException('Database connection failed');
}

function processOrder(): void
{
    $resourceOpen = true;

    try {
        saveOrder();
    } catch (RuntimeException $error) {
        // Add domain meaning and preserve the original cause.
        throw new OrderProcessingException(
            'The order could not be processed',
            0,
            $error
        );
    } finally {
        // Keep cleanup small and reliable.
        if ($resourceOpen) {
            $resourceOpen = false;
            echo "Resource closed\n";
        }
    }
}

try {
    processOrder();
} catch (OrderProcessingException $error) {
    // Handle the expected domain failure first.
    echo $error->getMessage() . "\n";

    $previous = $error->getPrevious();

    if ($previous !== null) {
        echo 'Original cause: ' . $previous->getMessage() . "\n";
    }
} catch (Throwable $error) {
    // Final boundary for unexpected thrown failures.
    error_log((string) $error);
    echo "An unexpected failure occurred\n";
}
Where it is used

This behavior is used in HTTP request entry points, command line commands, queue workers, scheduled jobs, database transaction services, payment operations, and external service clients. A lower level component may throw RuntimeException or another specific exception. A service can translate it into a domain specific exception while preserving the original Throwable as the previous cause. The outer boundary can then log the complete chain, release resources, return a safe response, and ensure that the request, command, or job is still recorded as failed.

Why Interviewers Ask This

Interviewers ask this question to test whether a candidate understands how modern PHP represents thrown failures and moves control through try, catch, and finally blocks. It also tests whether the candidate can distinguish application exceptions from engine errors, order handlers correctly, preserve an original cause, design useful domain exceptions, and handle unexpected failures at a production boundary without hiding defects or reporting false success.

Common interview mistakes

Common mistakes include catching Throwable inside every method, placing Throwable or Exception before a more specific handler, and assuming that every warning or notice is an Error object. Another mistake is treating Error as a normal business outcome that should always be ignored or recovered from. Developers may also wrap an exception without passing the original Throwable as the previous cause, which loses useful diagnostic context. Returning or throwing from finally can replace an earlier return or failure. Logging a Throwable and then reporting success is also incorrect. Exceptions should not be used for common branches when a normal condition or return value expresses the result more clearly.

Interview tip

Explain the hierarchy first: Throwable is the common interface, and Exception and Error are its main branches. State that user exception classes extend Exception rather than implementing Throwable directly. Then explain specific catch ordering, reliable finally cleanup, the difference between rethrowing and wrapping, cause preservation through the previous argument, and broad Throwable handling at an outer boundary. Mention that traditional warnings and notices are not automatically Throwable objects.

Interviewer may ask next
What happens when both the try block and the finally block throw a Throwable?

The Throwable created in finally is the one propagated out of the construct. PHP places the earlier Throwable from try into the previous chain of the later Throwable. This matters because unreliable cleanup can change the visible failure and make diagnosis harder. The main tradeoff is that finally guarantees a place for cleanup, but code inside it must remain small and should avoid throwing unless the cleanup failure truly must replace the active result.

Should a service catch Throwable broadly to prevent the application from stopping?

No. A service should normally catch only the specific failures it can recover from or translate into meaningful domain exceptions. A broad Throwable catch belongs mainly at an outer request, command, or worker boundary where the application can log the failure, clean up, and return or record a failed result. Broad catches in every service add unnecessary handling work, can hide TypeError and other programming defects, and make control flow harder to understand. They do not remove the cost of creating the Throwable or recording its diagnostic trace.

25. How do PHP attributes work, and when would you use them?Language SpecificMedium

Question Details

Explain declaring and targeting attributes, reflection-based reading, repeatable attributes, constructor arguments, metadata use cases, and runtime costs.

Short Interview Answer (30-60 seconds)

PHP attributes attach structured metadata to declarations such as classes, methods, properties, parameters, functions, and constants. I declare an attribute class with #[\Attribute], select its allowed targets, and accept constant expression arguments through its constructor. Attributes do nothing by themselves. Application or framework code must read them through reflection. I use them for stable code related metadata such as routes, validation rules, and serialization settings. I normally process and cache that metadata instead of repeating reflection and object creation in a hot path.

Detailed Explanation

See the Code while reading this explanation.

PHP attributes let developers place structured information beside the part of a program that the information describes. For example, a method can carry information saying which web address should call it. PHP stores this information, but it does not perform the requested action automatically. Another part of the application must read the information and decide what to do. This makes attributes useful for rules that belong closely to source code and change when that code changes. Useful questions for the interviewer are:

Useful Questions to Ask the Interviewer
  1. Which declarations must the attribute support?
  2. Can it appear more than once on one declaration?
  3. When and how often will the application read it?
How do PHP attributes work, and when would you use them? diagram
How to Explain It in an Interview

An attribute class is marked with #[\Attribute]. Its constructor defines the values that callers may supply. Attribute arguments may be positional or named, but they must be literal values or constant expressions. For example, #[Route(path: '/users', method: 'GET')] supplies values that can later be passed to the Route constructor.

The flags passed to #[\Attribute] restrict valid targets. PHP supports targets for classes, functions, methods, properties, class constants, parameters, and, beginning with PHP 8.5, global constants. Multiple target flags can be combined with the bitwise OR operator. Without an explicit target, the default is Attribute::TARGET_ALL. Attribute::IS_REPEATABLE allows the same attribute class to appear more than once on one declaration.

Reflection methods such as ReflectionClass::getAttributes and ReflectionMethod::getAttributes return ReflectionAttribute objects. getName returns the attribute class name. getArguments returns the stored arguments without creating the attribute object. newInstance creates the object and invokes its constructor.

An important edge case is deferred validation. getAttributes can return metadata even when an attribute uses an invalid target or is repeated without Attribute::IS_REPEATABLE. PHP reports that problem when newInstance is called. newInstance can also fail if the attribute class is missing or its constructor arguments are invalid.

Attributes are passive metadata. A Route attribute does not register a route by itself. A router must read it and build a route table. Good uses include routing, validation, event registration, dependency injection hints, test markers, and serialization rules. External configuration is usually better for values that operators must change without editing and deploying source code.

Reflection scans and each newInstance call take runtime work. Each created attribute object also uses memory. The exact cost depends on the number of declarations, attributes, arguments, and objects, so a fixed cost should not be claimed. Production systems commonly process attributes during startup, cache warming, container building, or the first lookup, then reuse a compact cached result. Long running workers must refresh that cache when deployed code changes.

Example

The example defines a repeatable Route attribute that is valid only on methods. Its constructor receives a path and an HTTP method. One controller method has two Route declarations. Reflection reads the matching metadata and calls newInstance for each declaration. That call creates each Route object and runs its constructor. The application then prints the values. This demonstrates that PHP stores metadata while application code remains responsible for interpreting it.

Code
<?php

declare(strict_types=1);

#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class Route
{
    public function __construct(
        public readonly string $path,
        public readonly string $method
    ) {
        if ($path === '') {
            throw new \InvalidArgumentException('The route path cannot be empty.');
        }

        if ($method === '') {
            throw new \InvalidArgumentException('The HTTP method cannot be empty.');
        }
    }
}

final class UserController
{
    #[Route(path: '/users', method: 'GET')]
    #[Route(path: '/members', method: 'GET')]
    public function listUsers(): void
    {
    }
}

$reflection = new \ReflectionMethod(UserController::class, 'listUsers');

foreach ($reflection->getAttributes(Route::class) as $attribute) {
    // This creates the Route object and invokes its constructor.
    $route = $attribute->newInstance();

    echo $route->method . ' ' . $route->path . PHP_EOL;
}
Where it is used

Attributes are used in production for HTTP route definitions, validation constraints, event listener registration, dependency injection hints, object serialization names, authorization metadata, command registration, and test discovery. They work best for stable metadata that belongs beside a declaration. Applications commonly scan them during startup or cache building and store a simpler lookup structure for later requests.

Why Interviewers Ask This

Interviewers ask this to check whether the candidate understands how PHP represents structured metadata, how reflection discovers it, and when PHP validates and creates attribute objects. It also tests practical judgment about target restrictions, repeatable attributes, constructor arguments, runtime cost, memory cost, caching, and suitable production use cases.

Common interview mistakes

A common mistake is assuming PHP automatically performs the behavior described by an attribute. Attributes only provide metadata. Another mistake is assuming target and repeatability rules are always rejected when PHP parses the declaration. For user defined attributes, those rules are validated when newInstance is called. Developers may also forget Attribute::IS_REPEATABLE, use arguments that are not constant expressions, place slow work or external calls inside an attribute constructor, create attribute objects repeatedly in a hot path, or use attributes for settings that should be changed without a code deployment.

Interview tip

Start by saying that attributes are passive structured metadata. Then explain the attribute class, target flags, constructor arguments, reflection, repeatability, and deferred validation. Clearly distinguish getArguments from newInstance. Finish with one production use case and explain that repeated reflection and object creation should usually be replaced by cached processed metadata.

Interviewer may ask next
When are invalid targets and nonrepeatable attribute uses detected?

They are detected when ReflectionAttribute::newInstance is called. Reflection getAttributes can still return a ReflectionAttribute for a declaration that uses the wrong target or repeats an attribute that lacks Attribute::IS_REPEATABLE. newInstance validates the attribute use while creating the object and throws an Error when the use is invalid. This matters because discovery and validation are separate steps, so production code should validate attributes while building its metadata cache rather than waiting for a later request.

What are the runtime and memory tradeoffs of reading attributes in production?

Reflection scanning takes runtime work, and every newInstance call allocates an attribute object and invokes its constructor. Keeping those objects or large processed maps also consumes memory. The exact amount depends on the number of declarations, attributes, arguments, and retained results. A common tradeoff is to scan once during startup or cache warming and store only the compact data needed at runtime. This reduces repeated work but requires cache invalidation after code changes and lifecycle care in long running workers.

26. How do generators work in PHP?Language SpecificMedium

Question Details

Explain yield, lazy iteration, keys and values, send or return behavior where relevant, memory benefits, one-pass limitations, and appropriate use cases.

Short Interview Answer (30-60 seconds)

PHP generators let a function produce one key and value at a time with yield instead of building and returning a complete collection. Calling the function creates a Generator object, but the function body starts only when iteration begins. PHP pauses at each yield and keeps enough local state to continue later. This can greatly reduce memory when processing large or unbounded streams, but a generator is normally consumed in one pass and does not provide random access like an array.

Detailed Explanation

See the Code while reading this explanation.

A generator is useful when a program has many items but only needs to handle one item at a time. Instead of preparing every result before work can begin, the program prepares the next result only when it is requested. This can keep the program responsive and avoid holding a large collection in memory. The main questions are how each result is produced, whether the results must be read more than once, and whether the caller needs to look up any result directly.

Useful Questions to Ask the Interviewer
  1. Does the caller need one pass or several passes over the data?
  2. Must the caller access items by position at any time?
  3. Can the data source itself be read gradually?
  4. Does the caller need to send a value back into the generator?
How do generators work in PHP? diagram
How to Explain It in an Interview

A PHP function becomes a generator when its body contains yield. Calling that function returns a Generator object. PHP does not immediately run the complete function body. Execution starts when the generator is first inspected or advanced, such as through foreach, current, next, or send.

When PHP reaches yield, it exposes a value to the caller and pauses the function. Local variables and the current execution position remain available. When the caller advances the generator, PHP continues immediately after that yield. A yield can provide only a value, or it can provide an explicit key and value with yield $key => $value. Foreach receives those keys and values in the normal way.

A yielded expression can also receive data. Generator::send passes a value into the suspended yield expression and resumes execution. If the generator has not started, send first advances it to its initial yield. The generator function may use return to provide one final result. That result is not another yielded item. The caller reads it with getReturn only after the generator has finished.

The main memory benefit is that yielded items do not all need to exist in one PHP array at the same time. Memory normally depends on the generator state, the current item, and any buffers retained by the data source or caller. Therefore, a generator does not guarantee constant memory if the function, database driver, parser, or consumer still stores all results.

A generator is normally a one pass iterator. After it has advanced beyond its first yield, it cannot be rewound to the beginning. It also does not provide array style random access or a known count unless the application calculates that information separately. Use generators for large files, paged records, streamed input, pipelines, and sequences created as needed. Prefer an array when the data is small or must support repeated traversal, direct lookup, sorting, or several transformations.

Example

The example creates a generator that first yields a prompt. The caller starts it with current, then sends the step value into the suspended yield expression. The generator produces three square values with explicit keys. Each call to next resumes the function until the next yield. After the final value, the function returns a summary string. The caller reads that final return value with getReturn only after valid becomes false. The example uses manual advancement because send has already moved the generator beyond its initial yield.

Code
<?php

declare(strict_types=1);

function squareSequence(): Generator
{
    // Pause here and ask the caller to provide the step size.
    $step = yield 'request' => 'Send a positive step value';

    if (!is_int($step) || $step <= 0) {
        throw new InvalidArgumentException('Step must be a positive integer.');
    }

    // Produce one key and value at a time.
    for ($number = $step; $number <= $step * 3; $number += $step) {
        yield $number => $number * $number;
    }

    // This is the final generator result, not another yielded item.
    return 'Produced three square values';
}

$generator = squareSequence();

// current starts the generator and pauses at its first yield.
echo $generator->current() . PHP_EOL;

// send places 2 into the suspended yield expression and resumes execution.
$generator->send(2);

// Read each produced key and value without rewinding the generator.
while ($generator->valid()) {
    echo $generator->key() . ' => ' . $generator->current() . PHP_EOL;
    $generator->next();
}

// getReturn is valid after the generator has completed.
echo $generator->getReturn() . PHP_EOL;
Where it is used

Generators are useful for reading large files one record at a time, processing paged API results, walking database rows when the driver supports incremental fetching, creating data transformation pipelines, traversing large directory trees, and producing sequences that may be very large or have no fixed end. They are also useful when processing can begin before every result has been created. They are less suitable when callers need repeated iteration, direct item lookup, sorting of the complete result, or a reliable total count before processing.

Why Interviewers Ask This

Interviewers ask this question to check whether the candidate understands deferred execution, iteration state, controlled data production, and the difference between producing values one at a time and building a complete PHP array. It also tests judgment about memory usage, one pass processing, generator communication, and suitable production use cases.

Common interview mistakes

Common mistakes include saying that a generator creates all results in advance, assuming that it always uses constant memory, treating it as an array with random access, trying to iterate it again after it has been consumed, or calling getReturn before completion. Another mistake is confusing a value passed through send with a newly yielded value. send places the value into the suspended yield expression, resumes execution, and returns the next yielded value if one is reached. Developers should also remember that a lazy generator cannot reduce memory if another layer has already buffered the complete data set.

Interview tip

Explain the lifecycle in order: calling the function creates the Generator, iteration starts execution, yield exposes one key and value and pauses, advancing resumes the function, and return provides a final result through getReturn. Then state the main tradeoff clearly: lower collection memory and earlier processing in exchange for one pass behavior and no array style random access.

Interviewer may ask next
Can a PHP generator be rewound and iterated a second time?

No, a generator cannot normally be restarted after it has advanced beyond its first yield. It represents one suspended execution of one function call, so consumed values are not stored automatically for another pass. This matters when several consumers need the same data. In that case, create a new Generator by calling the generator function again, or materialize the values into an array when the extra memory cost is acceptable.

Does replacing an array with a generator always reduce production memory usage?

No, using a generator reduces memory only when the complete result is not stored elsewhere. The generator keeps its execution state and current values, but a database driver, file parser, application buffer, or consumer may still retain many or all items. The production benefit is strongest when every layer reads, transforms, and releases data gradually. The tradeoff is that lazy processing usually gives up repeated traversal, random access, and immediate knowledge of the complete result.

27. How does PHP copy-on-write affect arrays and strings?Language SpecificMedium

Question Details

Explain assignment without immediate copying, when mutation triggers separation, references, function calls, memory observations, and why the model matters for performance reasoning.

Short Interview Answer (30-60 seconds)

PHP normally uses copy on write for arrays and strings. Assigning one variable to another usually lets both values share the same internal data instead of copying all of it immediately. If one variable is later changed, PHP separates that value so the other variable remains unchanged. Explicit references made with an ampersand are different because both names refer to the same variable, so a change through either name is visible through both.

Detailed Explanation

See the Code while reading this explanation.

PHP avoids making a full duplicate each time one variable receives an array or a piece of text from another variable. Both variables can initially use the same saved information. This saves work when the second variable is only read. When one variable must change, PHP creates a separate value for that variable before applying the change. The original variable then keeps its old contents. This behavior is useful, but a later change can still require extra time and memory.

Useful Questions to Ask the Interviewer
  1. Should I compare normal assignment with assignment by reference?
  2. Should I include function arguments passed by value and by reference?
  3. Should I explain nested arrays and objects stored inside arrays?
  4. Should memory measurements be discussed as approximate observations?
How does PHP copy-on-write affect arrays and strings? diagram
How to Explain It in an Interview

PHP arrays and strings have value semantics. After normal assignment, changing the new variable must not change the original variable. PHP can provide this result without immediately duplicating all stored data.

For example, after $copy = $original, both variables may share the same internal array or string data. PHP keeps internal usage information for that shared data. Reading either variable does not require separation.

If code mutates one shared value, PHP separates it before applying the change. Changing an array element, appending an element, removing an element, or changing a string character can trigger this process. The cost of separation grows with the amount of container data or string data that must be copied.

Passing an array or string to a function by value follows the same principle. The function parameter may initially share internal data with the caller value. If the function only reads it, a full copy is normally unnecessary. If the function mutates its local parameter, PHP separates the local value and the caller value remains unchanged.

An explicit reference uses different semantics. With $second =& $first, both names refer to the same variable. A change through either name is visible through both names. This is aliasing, not normal copy on write value behavior.

Array separation copies the array container, but nested refcounted values may remain shared until they are themselves changed. Objects are also important because copying an array does not clone objects stored inside it. Both arrays can still contain handles to the same object.

Memory measurements are only observations. Exact numbers depend on the PHP memory manager, allocated capacity, reused blocks, and the specific build. Assignment is therefore not guaranteed to have zero cost, and a measured increase should not be treated as the exact size of a copied value.

Example

The example assigns an array and a string normally, then mutates only their copied variables. The original array and string remain unchanged because PHP separates the copied value when required. A function that accepts an array by value changes only its local parameter and returns the changed result. A function that accepts an array by reference changes the caller variable directly. The example also prints an observed memory difference around an array mutation, but it does not claim that the number equals the exact copied array size.

Code
<?php

declare(strict_types=1);

function changeByValue(array $items): array
{
    // The parameter may initially share internal array data.
    // This mutation separates the local value when required.
    $items['status'] = 'changed inside value function';

    return $items;
}

function changeByReference(array &$items): void
{
    // The ampersand allows direct mutation of the caller variable.
    $items['status'] = 'changed through reference';
}

$originalArray = [
    'status' => 'original',
    'count' => 10,
];

$copiedArray = $originalArray;

$memoryBeforeMutation = memory_get_usage();
$copiedArray['count'] = 20;
$memoryAfterMutation = memory_get_usage();

echo "Normal array assignment:\n";
echo "Original count: {$originalArray['count']}\n";
echo "Copied count: {$copiedArray['count']}\n";
echo 'Observed memory difference: '
    . ($memoryAfterMutation - $memoryBeforeMutation)
    . " bytes\n\n";

$originalString = 'PHP copy on write';
$copiedString = $originalString;
$copiedString[0] = 'X';

echo "Normal string assignment:\n";
echo "Original string: {$originalString}\n";
echo "Copied string: {$copiedString}\n\n";

$valueResult = changeByValue($originalArray);

echo "Function parameter passed by value:\n";
echo "Caller status: {$originalArray['status']}\n";
echo "Returned status: {$valueResult['status']}\n\n";

changeByReference($originalArray);

echo "Function parameter passed by reference:\n";
echo "Caller status: {$originalArray['status']}\n";
Where it is used

This behavior matters when production code works with large configuration arrays, decoded JSON data, database result arrays, request data, import records, templates, or long strings. Read only processing can avoid an immediate full copy. A later mutation can create a memory increase, which is important in command line imports, queue workers, batch jobs, and long running PHP processes. It also affects function design. Passing a value normally is appropriate when a function should not change the caller variable. Passing by reference is appropriate only when changing the caller variable is an intentional and documented part of the function contract.

Why Interviewers Ask This

Interviewers ask this question to check whether the candidate understands the difference between visible PHP value behavior and the internal work performed by the engine. A strong answer explains shared storage, separation during mutation, value parameters, explicit references, nested values, and realistic memory observations. It also shows whether the candidate can reason about performance without incorrectly claiming that every assignment immediately copies the complete array or string.

Common interview mistakes

A common mistake is saying that every assignment immediately copies the complete array or string. Another mistake is saying that all PHP variables are references. Normal value assignment, explicit references, and object handle behavior are different concepts. Candidates may also claim that passing a large array by value always copies it before the function begins. PHP can delay the copy until a mutation requires separation. Another mistake is assuming that copying an array clones objects stored inside it. The copied array can still contain handles to the same objects. Developers may also treat memory_get_usage output as an exact copy size, even though the memory manager can reserve and reuse memory. Finally, adding references only as a performance trick can create unexpected shared changes and make the code harder to reason about.

Interview tip

Start with the practical result: normal assignment does not immediately require a full copy, but mutation can trigger separation. Then contrast normal assignment, a value function parameter, and an explicit reference. Mention nested objects and approximate memory measurements to show complete runtime understanding.

Interviewer may ask next
What happens when a copied PHP array contains another array or an object and a nested value is changed?

The top level array follows copy on write, but copying it does not immediately create fully independent copies of every nested value. A nested array may remain shared until that nested array is mutated, at which point its own separation can occur. An object is different because the array stores an object handle. Copying the array does not clone the object, so changing an object property through either array can affect the same object. This matters because a copied outer array is not automatically a deep copy.

Should a function accept a large array by reference only to avoid a copy?

No. A value parameter can initially share the array data with the caller, so calling the function does not automatically require a complete array copy. A reference changes the function contract because the function can directly mutate the caller variable. It should be used only when that caller mutation is intentional. The tradeoff is that a reference may support direct updates, but it introduces shared mutable state and makes the code more difficult to understand and maintain.

28. What are the risks and semantics of PHP references?Language SpecificHard

Question Details

Explain reference sets, assignment by reference, foreach reference leakage, function parameters and returns by reference, interaction with arrays, and why references are not pointers.

Short Interview Answer (30-60 seconds)

PHP references create aliases, so multiple variable names can access the same variable content. They are not raw memory pointers. Changing the content through one alias is visible through every name in the same reference set. I use references only when shared mutation is intentional because they can hide side effects. The most common risk is a foreach variable that remains linked to the last array element until it is unset.

Detailed Explanation

See the Code while reading this explanation.

PHP normally lets code work with values without making two variable names permanently connected. A reference creates that connection. After the connection is made, changing the value through either name changes what both names see. This is sometimes useful when a function or loop must update an existing value directly. It is also risky because a change in one place can unexpectedly affect another place. Arrays make this especially important because individual elements can be connected, copied, or changed through loop variables.

Useful Questions to Ask the Interviewer
  1. Should the example modify the caller's original value?
  2. Should array element reference behavior be included?
  3. Should returning by reference be demonstrated in code?
What are the risks and semantics of PHP references? diagram
How to Explain It in an Interview

A PHP reference is an alias to the same variable content. When $second =& $first runs, both names join the same reference set. Neither variable points to the other. Both names access the same content. Assigning a new value through either name changes what every member of that set observes. PHP references do not expose memory addresses and do not support pointer arithmetic. ([php.net](https://www.php.net/manual/en/language.references.whatdo.php))

Normal assignment is different. With $copy = $array, PHP uses value semantics and normally delays copying the array storage until one side is changed. This is copy on write behavior. References should therefore not be added merely to avoid copying. They add alias tracking, may interfere with simple value reasoning, and do not provide a reliable performance or memory improvement.

Array elements can belong to reference sets. A subtle case occurs when an array containing a referenced element is copied. The copied arrays can still contain elements connected to the same referenced content. Updating that element through one array may therefore affect the other array.

In foreach ($items as &$item), the loop variable becomes an alias to each element in turn. After the loop, it remains linked to the last element. A later assignment to $item can overwrite that element. Calling unset($item) removes that variable name from the relationship and prevents this leakage. ([php.net](https://www.php.net/manual/es/control-structures.foreach.php))

A reference parameter is declared in the function signature, such as function update(array &$data): void. The caller passes a variable without adding & at the call site. The function can then change the caller's variable. Expressions and literal values cannot normally be supplied where a referenceable variable is required. ([php.net](https://www.php.net/references.pass))

A function that returns by reference places & before its name and must return a variable. The caller also uses =& to bind to the returned variable. Returning references is uncommon and should only be used when the caller truly needs an alias to existing storage, not as a performance optimization. ([php.net](https://www.php.net/references.return))

Example

The example first creates a reference set between two scalar variable names. It then shows that an array element can remain shared after the array is copied when that element belongs to a reference set. Next, it updates array elements with foreach by reference and immediately unsets the loop variable. It also demonstrates a reference parameter that changes the caller's array and a function that returns a reference to an existing array element. Every mutation is intentional and visible in the printed output.

Code
<?php

declare(strict_types=1);

// Two names join the same reference set.
$first = 10;
$second =& $first;
$second = 20;

echo "Reference set: {$first}, {$second}\n";

// An array element can itself belong to a reference set.
$sharedStatus = 'pending';
$original = ['status' => &$sharedStatus];
$copied = $original;

// Both elements still refer to the shared content.
$copied['status'] = 'approved';

echo "Original status: {$original['status']}\n";
echo "Copied status: {$copied['status']}\n";

// foreach by reference changes the original elements.
$numbers = [1, 2, 3];

foreach ($numbers as &$number) {
    $number *= 10;
}

// Remove the loop variable alias to the last element.
unset($number);

// This no longer changes the last array element.
$number = 999;

echo 'Numbers: ' . json_encode($numbers, JSON_THROW_ON_ERROR) . "\n";

// A reference parameter changes the caller's variable.
function markActive(array &$record): void
{
    $record['active'] = true;
}

$user = ['name' => 'Asha'];
markActive($user);

echo 'User: ' . json_encode($user, JSON_THROW_ON_ERROR) . "\n";

// A return by reference must return an existing variable.
function &statusSlot(array &$record): mixed
{
    return $record['status'];
}

$order = ['status' => 'new'];
$orderStatus =& statusSlot($order);
$orderStatus = 'sent';

echo "Order status: {$order['status']}\n";
Where it is used

References are used when an API intentionally changes a caller owned variable, when a foreach loop must update array elements in place, or when an interface must expose an alias to existing variable storage. They may also appear in older PHP libraries and callback APIs. In production code, references should stay inside small and clearly documented boundaries. Returning a new value is usually easier to test and understand when shared mutation is not required.

Why Interviewers Ask This

Interviewers ask this question to test whether the candidate can distinguish normal value assignment, explicit reference relationships, and object handle behavior in PHP. It also checks knowledge of reference sets, array behavior, foreach leakage, function parameter and return rules, memory tradeoffs, and production bugs caused by hidden shared mutation.

Common interview mistakes

A common mistake is saying that all PHP variables are references. Normal assignment uses value semantics, while an explicit reference relationship uses &. Another mistake is describing references as raw memory pointers. Developers may also forget to unset a foreach reference variable, add & at a function call instead of only in the parameter declaration, pass a literal or expression to a reference parameter, or return an expression instead of a variable from a function that returns by reference. A less obvious mistake is assuming that copying an array always breaks every relationship involving referenced elements.

Interview tip

Begin with the practical rule that references create aliases and should be used only for intentional shared mutation. Then contrast them with normal value assignment and object handle behavior. Mention reference sets, array element sharing, foreach leakage, parameter syntax, return syntax, and why references are not a performance shortcut.

Interviewer may ask next
What can happen when an array containing a referenced element is copied?

The referenced element can remain connected to the same variable content in both arrays. Changing that element through one array may therefore be visible through the other array. This matters because normal array copying suggests independent value behavior, but an embedded reference preserves shared mutation for that element. The safest approach is to avoid storing references in arrays unless that sharing is explicitly required.

Should a large array be passed by reference only to save memory or improve speed?

No. PHP normally uses copy on write behavior for arrays, so passing or assigning an array by value does not always create an immediate full copy. A reference is therefore not a guaranteed memory or speed optimization. It adds alias tracking and shared mutation, which can make code harder to reason about. Use a reference only when the function must intentionally replace or modify the caller's variable.

29. How does PHP's garbage collector handle cyclic references?Language SpecificHard

Question Details

Explain reference counting, why cycles require a collector, collection roots, when collection runs, observability, and implications for long-running workers.

Short Interview Answer (30-60 seconds)

PHP normally releases a value when its reference count reaches zero. A cycle is different because the values still point to each other, so their counts can stay above zero even when the application can no longer reach them. PHP records possible cycle roots and periodically scans them. Unreachable groups are then collected. In a long running worker, I would remove unnecessary references, watch memory and collector statistics, and request manual collection only at measured lifecycle boundaries.

Detailed Explanation

See the Code while reading this explanation.

This question asks how PHP removes unused values that still point to each other. Normally, PHP can remove a value when no part of the program uses it. A circular connection is harder because each value still appears to be used by another value in the same group. PHP therefore records suspicious values and checks whether anything outside the group can still reach them. This matters most in programs that remain active for a long time because unused circular groups can otherwise keep memory occupied between units of work.

Useful Questions to Ask the Interviewer
  1. Are we discussing the standard PHP 8.4 and PHP 8.5 runtime?
  2. Should I cover long running command line workers?
  3. Should I explain the functions for observing and requesting collection?
How does PHP's garbage collector handle cyclic references? diagram
How to Explain It in an Interview

PHP mainly manages values through reference counting. A managed value has a count that tracks references to it. When that count becomes zero, PHP can normally destroy the value immediately.

A cyclic reference prevents this simple rule from being enough. For example, object A can refer to object B while object B refers to object A. After the application removes both outside variables, the two objects still refer to each other. Their reference counts therefore do not reach zero, even though the application cannot reach the objects anymore.

PHP handles this case with its cyclic garbage collector. When a reference count is reduced but remains above zero, the related value can become a possible cycle root. PHP records possible roots in an internal buffer. During a collection run, PHP examines the connected candidate values and accounts for references that come from inside the candidate graph. Values that have no remaining reference from reachable application data are identified as unreachable and can be destroyed.

Automatic collection runs when the collector reaches its current root threshold. Modern PHP exposes the current threshold and number of buffered roots through gc_status(). The threshold should not be described as a permanent fixed value because modern runtimes can adjust collector capacity and thresholds.

The function gc_collect_cycles() requests a collection run and returns a count reported by the collector. In PHP 8.5, that return value no longer includes strings and resources that were collected indirectly through cycles. The functions gc_enable(), gc_disable(), and gc_enabled() control or report automatic cycle collection.

Collection reduces retained memory, but a collection run also takes processing time because PHP must inspect candidate graphs. Calling it after every small operation is usually unnecessary. Long running queue workers, command line services, and persistent application servers should clear job references, avoid accidental cycles, inspect gc_status(), watch real memory trends, and use manual collection only when measurement supports it.

Example

The example creates two Node objects that refer to each other. It then removes the two outside variables. Reference counting alone cannot immediately release the objects because each object still holds a reference to the other. gc_collect_cycles() requests a cycle collection run and returns the count reported by the current PHP runtime. The code does not assume one exact count because PHP 8.5 changed which indirectly collected strings and resources are included in that return value. gc_status() then displays collector state and timing information available in modern PHP.

Code
<?php

declare(strict_types=1);

final class Node
{
    public ?Node $other = null;
}

// Create two objects that refer to each other.
$first = new Node();
$second = new Node();
$first->other = $second;
$second->other = $first;

// Remove the references held by the application.
unset($first, $second);

// Request a scan for unreachable reference cycles.
$reportedCount = gc_collect_cycles();

echo "Collector reported count: {$reportedCount}\n";

// Inspect the current collector state and statistics.
print_r(gc_status());
Where it is used

This behavior appears in object graphs with links in both directions, such as a parent that stores children while each child stores its parent. It can also appear in event listener registries, closures that capture their owning object, dependency graphs, tree structures, and caches that connect objects to each other. It matters most in queue consumers, command line daemons, persistent application servers, test runners, and other processes that handle many tasks without exiting. Normal short PHP requests usually release request memory at the end, but avoiding unnecessary retained references is still good practice.

Why Interviewers Ask This

Interviewers ask this question to check whether the candidate understands how PHP combines reference counting with a separate cycle collector. It also tests knowledge of collection roots, collection timing, diagnostic functions, version differences, memory behavior, and the lifecycle risks of long running PHP workers.

Common interview mistakes

A common mistake is saying that unset always destroys a value immediately. unset removes one variable reference, but references inside a cycle can keep the counts above zero. Another mistake is saying that all PHP memory is managed only by the cyclic collector. Reference counting remains the main immediate cleanup mechanism, while the collector handles unreachable cycles. It is also incorrect to describe the root threshold as a permanent fixed number in modern PHP. Developers may call gc_collect_cycles() after every job without measuring its cost, or disable automatic collection and forget that unreachable cycles can accumulate. Another mistake is expecting the operating system memory number to fall immediately after collection. PHP can release values while its memory manager keeps allocated regions available for reuse by the same process.

Interview tip

Begin with the key contrast. Reference counting handles ordinary cleanup, but circular references need a separate reachability scan. Then explain possible roots, the internal root buffer, the current collection threshold, gc_collect_cycles(), gc_status(), the PHP 8.5 return value change, and why long running workers require measurement and lifecycle discipline.

Interviewer may ask next
What happens when automatic cyclic garbage collection is disabled?

Normal reference counting still works, but automatic scans for unreachable cycles stop. Values whose reference counts reach zero can still be destroyed immediately. Unreachable cycles can remain allocated because their members continue to reference one another. gc_collect_cycles() can still be called explicitly to request a collection run. This matters in a long running process because retained cycles can increase memory use. Disabling automatic collection may avoid a collection run during a sensitive section, but it transfers timing and memory responsibility to the application.

Should a long running PHP worker call gc_collect_cycles() after every job?

No, not by default. Each collection run must inspect candidate roots and connected values, so unnecessary calls add processing work. The worker should first remove job references, avoid retaining callbacks and object graphs, inspect gc_status(), and measure memory across many jobs. A manual collection call can be placed at a sensible job or batch boundary when measurements show that cyclic data is accumulating. The tradeoff is lower retained memory against added collector work and possible latency during the collection run.

30. How do weak references and WeakMap work in PHP?Language SpecificHard

Question Details

Explain how they differ from strong references, garbage-collection behavior, object-keyed metadata use cases, limitations, and examples where they prevent unintended retention.

Short Interview Answer (30-60 seconds)

WeakReference and WeakMap let PHP access objects without keeping those objects alive. WeakReference observes one object, and its get method returns that object while it exists or null after it is destroyed. WeakMap stores values under object keys without increasing the key reference count, so PHP removes an entry when its key has no remaining strong reference. I use them for optional observation, derived caches, and object metadata that must not extend an object lifetime.

Detailed Explanation

See the Code while reading this explanation.

This question asks how PHP can watch an object or attach extra information to it without forcing that object to stay in memory. A normal variable keeps an object available while the variable still points to it. These features do not provide that ownership. The object may disappear when the rest of the program stops using it. This is useful for temporary caches and information connected to short lived objects. It can prevent a service from keeping objects only because it forgot to remove old entries.

Useful Questions to Ask the Interviewer
  1. Do we need to observe one object or store data for many objects?
  2. Should the stored data disappear when its object disappears?
  3. Is this code used in a normal request or a long running process?
How do weak references and WeakMap work in PHP? diagram
How to Explain It in an Interview

A normal PHP variable that contains an object creates a strong reference to that object. The object stays alive while a reachable strong reference remains.

WeakReference observes one object without increasing its reference count. Create it with WeakReference::create($object). Its get method returns the object while it is alive. It returns null after PHP destroys the object, so the caller must always handle both results. WeakReference cannot be serialized. ([php.net](https://www.php.net/manual/en/class.weakreference.php))

WeakMap is a collection whose keys must be objects. Values may be any PHP value. The map holds each value strongly, but it does not increase the reference count of the object used as its key. When no strong reference to a key remains, PHP destroys that key and automatically removes its entry from the map. ([php.net](https://www.php.net/weakmap))

PHP normally destroys an object when its reference count reaches zero. An unreachable object cycle may remain until the cycle collector processes it. Weak references do not keep an otherwise unreachable object alive.

WeakMap is useful for derived cache results, validation state, serializer state, and metadata linked to object instances. It avoids manual entry cleanup when object lifetime is the correct cleanup rule.

A major limitation is that a value can retain its own key. For example, if a WeakMap value contains a strong reference back to the key object, that reference keeps the key alive. The entry therefore remains until that strong path is removed.

Do not use weak storage when the collection must own the objects or preserve data after a key disappears. It also does not replace explicit cleanup for files, sockets, transactions, or other external resources.

Example

The example creates a Service object and a WeakReference that observes it. The first call to get returns the Service because the service variable is a strong reference. After that variable is removed, the Service is destroyed and get returns null. The example then creates a WeakMap and stores metadata under a Request object key. The map contains one entry while the request variable exists. Removing that final strong reference destroys the Request and automatically removes the related map entry.

Code
<?php

declare(strict_types=1);

final class Service
{
}

final class Request
{
}

// WeakReference observes an object without keeping it alive.
$service = new Service();
$serviceReference = WeakReference::create($service);

var_dump($serviceReference->get() instanceof Service);

// Remove the final strong reference to the Service object.
unset($service);

var_dump($serviceReference->get());

// WeakMap stores a value under an object key without retaining the key.
$metadata = new WeakMap();
$request = new Request();
$metadata[$request] = ['validated' => true];

var_dump(count($metadata));
var_dump($metadata[$request]);

// Remove the final strong reference to the Request object.
unset($request);

var_dump(count($metadata));
Where it is used

WeakReference is useful when an event system, diagnostic tool, or object coordinator needs to observe one object without owning its lifetime. WeakMap is useful for validation results, serializer state, computed values, proxy information, and temporary metadata associated with object instances. These features are especially valuable in long running workers because strong object storage can unintentionally retain every processed object. In a normal PHP request, they can still express correct ownership, although request termination already releases request memory.

Why Interviewers Ask This

Interviewers ask this question to check whether a candidate understands PHP object lifetime, reference counting, cycle collection, and unintended memory retention. It also tests whether the candidate can choose between WeakReference, WeakMap, and strong object storage in caches, metadata systems, and long running PHP processes.

Common interview mistakes

A common mistake is assuming that WeakReference::get always returns an object. It can return null as soon as the target has been destroyed. Another mistake is treating WeakMap as a normal PHP array. WeakMap accepts only object keys, and entries depend on key lifetime. Developers may also assume that its values are weak, but values are held strongly while their entries exist. A value that strongly refers back to its key can keep the key alive and prevent automatic removal. Calling gc_collect_cycles after every unset is also unnecessary for objects that are not part of unreachable cycles and can add avoidable runtime work.

Interview tip

Start with ownership. Explain that normal variables and strong collections keep objects alive, but WeakReference and WeakMap do not keep their target or key alive. Then distinguish one observed object from object keyed metadata. Finish with the null check, automatic entry removal, and the strong value back reference limitation.

Interviewer may ask next
What happens if a WeakMap value contains a strong reference to its own key object?

The key remains alive because the value provides a strong path back to it. WeakMap keeps the value strongly while the entry exists, so the key reference count does not reach zero. PHP therefore cannot destroy the key or automatically remove the entry. This matters because the back reference defeats the intended retention benefit and may cause memory growth in a long running process.

When should SplObjectStorage be used instead of WeakMap?

Use SplObjectStorage when the collection should strongly retain its object keys until entries are explicitly removed or the storage itself is destroyed. Use WeakMap when metadata should disappear automatically with each key object. SplObjectStorage gives deliberate ownership and predictable retention, while WeakMap reduces cleanup work and unintended retention. The tradeoff is that WeakMap data can disappear whenever no other strong reference to a key remains.

More questions load as you scroll

Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.

Content Accuracy and Verification: To the fullest extent permitted by applicable law, we do not represent or warrant that interview guides, questions, answers, examples, or diagrams are accurate, complete, current, error-free, or suitable for any particular purpose. You are responsible for independently reviewing and verifying the information before relying on it.