Merge Two YAML Configuration Trees with Per-Node Merge Rules

Read the full interview experience this question came from →

Quick Overview

Merge two configuration trees parsed from YAML files, where each node can follow its own merge rule, such as recursive merging, replacing, keeping, appending lists, or matching list items by a key field. Tests recursive traversal of two trees, an extensible way to apply many rules, and careful handling of ordering, one-sided keys and type conflicts.

Merge Two YAML Configuration Trees with Per-Node Merge Rules

Company: Google

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Technical Screen

You are given two configuration trees, each parsed from a YAML file: a **base** tree and an **overlay** tree. Implement a function that merges them into a single tree. After parsing, every node is a mapping (string keys to child nodes), a list of nodes, or a scalar (a string, number, boolean or null). The merge behavior is decided node by node: each node can have its own merge rule, and there are many rules. Walk both trees together and, at every pair of corresponding nodes, find the rule that applies and carry it out. The original statement described a long list of rules through lengthy worked examples, and that exact list was not preserved. Practice with the representative set below, and treat it as open-ended: adding a new rule should not require rewriting the traversal. | Rule | Applies to | Result | | --- | --- | --- | | `merge` | two mappings | Keys present on one side only are copied. Keys present on both sides are merged recursively, each under its own rule. | | `replace` | any two nodes | The overlay node. | | `keep` | any two nodes | The base node. | | `append` | two lists | The base items followed by the overlay items. | | `merge_by_key:<field>` | two lists of mappings | Items whose `<field>` values are equal are merged recursively. Base items keep their order; overlay items with no match follow, in overlay order. | Rules arrive as a table keyed by node path, for example `{"service.env": "merge_by_key:name"}`. A node with no entry in the table uses `merge` when both sides are mappings and `replace` otherwise. ```python Node = dict | list | str | int | float | bool | None def merge_trees(base: Node, overlay: Node, rules: dict[str, str]) -> Node: ... ``` **Example** Base file: ```yaml service: name: api replicas: 2 ports: [80] env: - name: LOG_LEVEL value: info - name: REGION value: us-east ``` Overlay file: ```yaml service: replicas: 5 ports: [443] env: - name: LOG_LEVEL value: debug - name: FEATURE_FLAG value: enabled ``` Rules: `{"service.ports": "append", "service.env": "merge_by_key:name"}` Merged result: ```yaml service: name: api replicas: 5 ports: [80, 443] env: - name: LOG_LEVEL value: debug - name: REGION value: us-east - name: FEATURE_FLAG value: enabled ``` ```hint Keep the walk and the rules apart Write the recursive walk so that it never needs to know what any particular rule does. Work out what information a rule needs from the walk in order to do its job. ``` ```hint Who decides whether to descend? Some rules need the children of a node merged one by one, and others produce their result without looking inside. Decide which part of your code should make that call. ``` ### Constraints and Clarifications - A path joins mapping keys from the root with dots, such as `service.env`. Every item of a list shares the list's path followed by `[]`, so `service.env[].value` addresses the `value` field of each item in `service.env`. The root's path is the empty string. - A rule only decides how a node that exists on both sides is combined. A key present on only one side is copied into the result unchanged. - In a merged mapping, the base keys come first in base order, followed by the overlay-only keys in overlay order. ### Clarifying Questions - How are rules attached in the real input: by exact path, by key name anywhere in the tree, by wildcard patterns, or by markers inside the YAML files themselves? If two of them match the same node, which wins? - What should happen when the two sides of a path have different types, such as a mapping in the base and a scalar in the overlay, or when a rule meets node types it does not support: fail, or let the overlay win? - Does an explicit `null` in the overlay set the value to null, or remove the key? - For `merge_by_key`, what if an item lacks the key field, or two items on the same side share a key value? - May the input trees be modified in place, or must the result be a new tree? ### What a Strong Answer Covers - Turns the long statement and its examples into a precise rule table, including defaults, before coding, and checks the design against every example - A depth-first merge that carries the current path, looks up the rule at each pair of nodes and dispatches through a registry, so a new rule touches one place - Correct handling of one-sided keys, list items matched by a key field, result ordering, and type mismatches, with error messages that name the path - A result that neither modifies the input trees nor shares mutable nodes with them - Running time linear in the number of nodes, awareness of the recursion depth limit, and tests drawn from the examples plus edge cases ### Follow-up Questions - Rules may now be patterns such as `*.env` or `services.*.ports`. How do you match them efficiently, and which rule wins when two patterns match one node? - Three files are layered: defaults, an environment file and a local override. Does merging them pairwise give the same result for either grouping? - Users ask which file each value in the result came from. How would you record that? - Add a `delete` rule, and let the overlay file request a rule for one of its own nodes. What changes in your design?

Overview: Merge two configuration trees parsed from YAML files, where each node can follow its own merge rule, such as recursive merging, replacing, keeping, appending lists, or matching list items by a key field. Tests recursive traversal of two trees, an extensible way to apply many rules, and careful handling of ordering, one-sided keys and type conflicts.

Read the full Google Software Engineer interview experience this question came from

|Home/Software Engineering Fundamentals/Google
Google logo
Google
Oct 9, 2026
mediumSoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
0
0

You are given two configuration trees, each parsed from a YAML file: a base tree and an overlay tree. Implement a function that merges them into a single tree.

After parsing, every node is a mapping (string keys to child nodes), a list of nodes, or a scalar (a string, number, boolean or null). The merge behavior is decided node by node: each node can have its own merge rule, and there are many rules. Walk both trees together and, at every pair of corresponding nodes, find the rule that applies and carry it out.

The original statement described a long list of rules through lengthy worked examples, and that exact list was not preserved. Practice with the representative set below, and treat it as open-ended: adding a new rule should not require rewriting the traversal.

RuleApplies toResult
mergetwo mappingsKeys present on one side only are copied. Keys present on both sides are merged recursively, each under its own rule.
replaceany two nodesThe overlay node.
keepany two nodesThe base node.
appendtwo listsThe base items followed by the overlay items.
merge_by_key:<field>two lists of mappingsItems whose <field> values are equal are merged recursively. Base items keep their order; overlay items with no match follow, in overlay order.

Rules arrive as a table keyed by node path, for example {"service.env": "merge_by_key:name"}. A node with no entry in the table uses merge when both sides are mappings and replace otherwise.

Node = dict | list | str | int | float | bool | None

def merge_trees(base: Node, overlay: Node, rules: dict[str, str]) -> Node:
    ...

Example

Base file:

service:
  name: api
  replicas: 2
  ports: [80]
  env:
    - name: LOG_LEVEL
      value: info
    - name: REGION
      value: us-east

Overlay file:

service:
  replicas: 5
  ports: [443]
  env:
    - name: LOG_LEVEL
      value: debug
    - name: FEATURE_FLAG
      value: enabled

Rules: {"service.ports": "append", "service.env": "merge_by_key:name"}

Merged result:

service:
  name: api
  replicas: 5
  ports: [80, 443]
  env:
    - name: LOG_LEVEL
      value: debug
    - name: REGION
      value: us-east
    - name: FEATURE_FLAG
      value: enabled

Constraints and Clarifications

  • A path joins mapping keys from the root with dots, such as service.env . Every item of a list shares the list's path followed by [] , so service.env[].value addresses the value field of each item in service.env . The root's path is the empty string.
  • A rule only decides how a node that exists on both sides is combined. A key present on only one side is copied into the result unchanged.
  • In a merged mapping, the base keys come first in base order, followed by the overlay-only keys in overlay order.

Clarifying Questions Guidance

  • How are rules attached in the real input: by exact path, by key name anywhere in the tree, by wildcard patterns, or by markers inside the YAML files themselves? If two of them match the same node, which wins?
  • What should happen when the two sides of a path have different types, such as a mapping in the base and a scalar in the overlay, or when a rule meets node types it does not support: fail, or let the overlay win?
  • Does an explicit null in the overlay set the value to null, or remove the key?
  • For merge_by_key , what if an item lacks the key field, or two items on the same side share a key value?
  • May the input trees be modified in place, or must the result be a new tree?

What a Strong Answer Covers Guidance

  • Turns the long statement and its examples into a precise rule table, including defaults, before coding, and checks the design against every example
  • A depth-first merge that carries the current path, looks up the rule at each pair of nodes and dispatches through a registry, so a new rule touches one place
  • Correct handling of one-sided keys, list items matched by a key field, result ordering, and type mismatches, with error messages that name the path
  • A result that neither modifies the input trees nor shares mutable nodes with them
  • Running time linear in the number of nodes, awareness of the recursion depth limit, and tests drawn from the examples plus edge cases

Follow-up Questions Guidance

  • Rules may now be patterns such as *.env or services.*.ports . How do you match them efficiently, and which rule wins when two patterns match one node?
  • Three files are layered: defaults, an environment file and a local override. Does merging them pairwise give the same result for either grouping?
  • Users ask which file each value in the result came from. How would you record that?
  • Add a delete rule, and let the overlay file request a rule for one of its own nodes. What changes in your design?