skip to content

In PHP, why can str_replace() with array arguments give a different result from strtr() given the same replacement pairs?

level: middleimportance: should knowfreq 38%

answer

  1. one pass or several passes
  2. earlier output is searched again
  3. strtr tries the longest key first
  4. replaced text is never re-scanned
  5. fourth argument counts replacements

basics

~20 s

str_replace() applies each search/replace pair in turn over the whole string, so text inserted by one pair can be replaced by a later pair. strtr() with an array makes one pass, tries the longest key first and never re-scans replaced text.

solid answer

~40 s

With arrays, `str_replace($search, $replace, $subject)` runs one full replacement per pair, in array order, and each step works on the previous step's output. So `str_replace(['A', 'B'], ['B', 'C'], 'AB')` returns `'CC'`: `A` becomes `B`, then both `B`s become `C`. `strtr($subject, ['A' => 'B', 'B' => 'C'])` scans the string once, at each position tries the **longest** matching key first, and never searches text it has already inserted, so it returns `'BC'`. That makes `strtr()` the safer choice for filling templates, where a value such as a customer name might contain another placeholder. `str_replace()` still fits single substitutions, arrays of subjects, and cases where you need the replacement count from its fourth, by-reference argument.

code

php · 17 lines
php
<?php
declare(strict_types=1);

$pairs = ['A' => 'B', 'B' => 'C'];
echo str_replace(array_keys($pairs), array_values($pairs), 'AB'), "\n"; // CC
echo strtr('AB', $pairs), "\n";                                          // BC

$template = 'Invoice {number}: {customer}';
$values = ['{customer}' => 'ACME {number} Ltd', '{number}' => 'INV-0042'];

echo str_replace(array_keys($values), array_values($values), $template), "\n";
// Invoice INV-0042: ACME INV-0042 Ltd  (inserted text was re-scanned)
echo strtr($template, $values), "\n";
// Invoice INV-0042: ACME {number} Ltd

$slashed = str_replace('-', '/', 'INV-2026-0042', $count);
echo $slashed, ' ', $count, "\n";                                        // INV/2026/0042 2

go deeper

for a junior

Recall the argument order of str_replace, that search and replace may be arrays, and that strtr also accepts an array of from-to pairs.

for a middle

Explain sequential passes versus a single longest-key-first pass, trace the AB example both ways, and describe the two-string byte mode of strtr.

for a senior

Show you would pick strtr for template filling so user-supplied values cannot trigger further substitution, and spot order-dependent str_replace calls in review.

for a principal

Weigh ad hoc placeholder replacement against a real templating layer with escaping, and set a team rule for which primitive fills user-visible documents.

## Two replacement models PHP has two general-purpose functions for replacing substrings, and they follow different models once you pass several pairs. - **`str_replace(array|string $search, array|string $replace, string|array $subject, &$count = null): string|array`** performs **sequential passes**. For each element of `$search`, in array order, it replaces every occurrence in the current subject, then moves to the next element working on the **updated** string. - **`strtr(string $string, array $replace_pairs): string`** performs a **single pass**. At each position it looks for a matching key, tries the **longest keys first**, writes the replacement, and continues after the matched text. Text it has already inserted is **never searched again**. | Aspect | `str_replace()` with arrays | `strtr()` with an array | |---|---|---| | Passes over the text | one per search element | one | | Inserted text re-scanned | yes, by later elements | no | | Overlapping keys | array order decides | longest key wins | | Case-insensitive variant | `str_ireplace()` (ASCII) | none | | Works on an array of subjects | yes | no | | Reports a count | yes, fourth argument | no | ## The chain effect The difference shows up whenever one replacement produces text that another search element matches: ```php str_replace(['A', 'B'], ['B', 'C'], 'AB'); // 'CC' strtr('AB', ['A' => 'B', 'B' => 'C']); // 'BC' ``` In the first call the `A` becomes `B`, giving `'BB'`, and then the second pass turns **both** `B`s into `C`. The manual lists this as the replacement-order gotcha of `str_replace()`. `strtr()` sees `A` at position 0 and `B` at position 1 in the original text and replaces each exactly once. ## Why it matters for templates Imagine an accounting export that fills an invoice header template: ```php $template = 'Invoice {number}: {customer}'; $values = ['{customer}' => 'ACME {number} Ltd', '{number}' => 'INV-0042']; ``` With `str_replace(array_keys($values), array_values($values), $template)` the `{customer}` pass inserts `'ACME {number} Ltd'`, and the following `{number}` pass rewrites the placeholder that came from the customer's own name. The output silently depends on the order of the array. With `strtr($template, $values)` the customer name is inserted verbatim, because inserted text is not searched again, and the order of the array is irrelevant. The longest-key rule helps with placeholders that share a prefix. Given colon-style keys `:total` and `:total_net`, `strtr()` always prefers `:total_net` where it matches, while `str_replace()` with `:total` listed first rewrites the `:total` part of `:total_net` and leaves `_net` behind. ## Other rules of str_replace() 1. If `$search` is an array and `$replace` is a **string**, every search element is replaced by that same string. 2. If `$replace` is an array **shorter** than `$search`, the missing replacements are the empty string, so `str_replace(['x', 'y'], ['1'], 'xyz')` returns `'1z'`. 3. If `$subject` is an array, the replacement runs on every element and an array is returned. 4. The fourth parameter is passed **by reference** and receives the total number of replacements made. `str_replace()` is case-sensitive; `str_ireplace()` has the same signature and matches ASCII letters case-insensitively. ## The two-string form of strtr() `strtr()` has a second, unrelated mode: `strtr($string, $from, $to)` with two strings translates **single bytes**. Each byte of `$from` is replaced by the byte at the same position in `$to`, and if the two strings differ in length the extra bytes of the longer one are ignored. `strtr('2026-09-29', '-', '/')` gives `'2026/09/29'`, but `strtr($s, 'ab', 'xyz')` does **not** replace the substring `ab` with `xyz`. Because it works on bytes, this form cannot translate multibyte UTF-8 characters. In the array form an empty-string key is ignored and raises a warning. ## Choosing between them - Filling placeholders, escaping a set of tokens, or any mapping where inserted text must stay untouched: **`strtr()` with an array**. - One search string, a batch of subjects, a need for the count, or case-insensitive matching: **`str_replace()`** or **`str_ireplace()`**. - Anything pattern-based, such as "every run of digits": that is regular-expression territory, not these functions. An interviewer asking this is checking whether you know that the order of an array can change the output of `str_replace()`, and whether you can name the function that removes the problem.

  • In PHP, what does strtr() do when it receives two strings instead of an array?
    It translates single bytes: each byte of the second argument is replaced by the byte at the same position in the third, and extra bytes in the longer string are ignored. It is not substring replacement, so `strtr($s, 'ab', 'xyz')` never inserts `xyz`, and it cannot map multibyte UTF-8 characters.
  • How does str_replace() handle a replace array that is shorter than the search array?
    Search elements without a counterpart are replaced by the empty string, so they are effectively deleted: `str_replace(['x', 'y'], ['1'], 'xyz')` returns `'1z'`. Pass a single string as `$replace` if every search element should map to the same text.
  • When is str_replace() still the better choice than strtr()?
    When there is one search string, when you need the number of replacements from the by-reference fourth argument, when the subject is an array of strings, or when you need ASCII case-insensitive matching through `str_ireplace()`. With a single pair the chain effect cannot occur.

saying these in an interview costs you the question

  • str_replace with arrays performs all replacements simultaneously in one pass.
  • strtr with an array applies the keys in the order they appear in the array.
  • strtr('abc', 'ab', 'xyz') replaces the substring ab with xyz.
  • str_replace matches letters case-insensitively by default.
  • The order of the search array never affects what str_replace returns.