{"id":89,"date":"2026-02-13T18:07:20","date_gmt":"2026-02-13T18:07:20","guid":{"rendered":"https:\/\/mattbick.dev\/?p=89"},"modified":"2026-02-12T18:31:47","modified_gmt":"2026-02-12T18:31:47","slug":"data-structure-every-day-day-6-binary-search-tree","status":"publish","type":"post","link":"https:\/\/mattbick.dev\/?p=89","title":{"rendered":"Data Structure Every Day, Day 6: Binary Search Tree"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This is the portion of data structures and algorithms that I got completely lost on as a third year ECE major. It was also THE FIRST thing they taught us in that class (why they wouldn&#8217;t start with linear data structures like arrays and lists is beyond me). Fortunately there are TONS of resources to brush up on tree data structures and thus the implementation of this was not as painful as I thought it was going to be. <\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Linear vs Tree Data Structures \u2014 Why Shape Matters<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Up until now, every data structure we\u2019ve implemented has been <strong>linear<\/strong>.<br>Linked lists, stacks, queues, and deques all share one core idea:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is exactly <strong>one path<\/strong> through the data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you imagine them visually, they look like a chain:<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">[ A ] -&gt; [ B ] -&gt; [ C ] -&gt; [ D ] -&gt; [ E ] \/\/ should remind you of our previous linked list implementations <\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Each element knows about at most one next element (and sometimes one previous element).<br>This makes them simple and predictable, but it also limits how efficiently we can search them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you want to find something in a linked list, you usually have to walk element by element:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Worst case:<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">O(N) \/\/ for other ops as we have shown, we can augment the linked list data structure with a tail or previous pointer to make other ops O(1)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is fine for small datasets, but once data gets large, linear traversal becomes expensive.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Enter Tree Data Structures<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Tree data structures break away from the idea of a single path.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of each element pointing to just one next element, tree elements can branch (hence the tree name):<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">        A\n       \/ \\\n      B   C\n     \/ \\\n    D   E<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The advantage here should become pretty clear. Let&#8217;s see we want to find the E Node. For our linked list, that means walking the entire list until we hit the E node. That is 5 ops. For this tree above, we simply visit A, then B, then we hit E (how this actually occurs will become more clear as we step into the specific definition of what makes a binary search tree a binary search tree). <\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Introducing the Binary Search Tree (BST)<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">So, how does that specific case above actually occur and optimize our search for a node? A Binary Search Tree is a specific kind of tree with two rules:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Rule 1 \u2014 Binary<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Each node has at most two children:<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">left child\nright child<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Rule 2 \u2014 Ordered (This Is The Important Part)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For every node:<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">left subtree  &lt;  node value  &lt;  right subtree \/\/ for out letters, it means being in alphabetical order. for numbers, we can just use more than or less than operators<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It is this second rule that really solidifies why our search for E case is faster for a BST vs a linear data structure. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Another Example:<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Search example:<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">        50\n       \/  \\\n     30    70\n    \/ \\   \/ \\\n   20 40 60 80<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Everything left of 50 is smaller.<br>Everything right of 50 is larger.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This rule is applied recursively throughout the tree.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So lets say we want to look for the value 60 in this tree:<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">Start at 50 \u2192 60 &gt; 50 \u2192 go right\nAt 70 \u2192 60 &lt; 70 \u2192 go left\nFound 60<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of checking every element, you eliminate half the tree at each step (in ideal cases where we have a balanced tree). <\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">The Three Core BST Operations<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">BSTs are defined primarily by three operations:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Insert<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Place a new value in the correct position while maintaining ordering.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Search<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Find whether a value exists by following left\/right decisions.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Delete<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Remove a value while preserving BST structure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is the hardest operation because removing nodes can break ordering if done incorrectly.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">The Hidden Fourth Operation: Traversal<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Traversal isn\u2019t always listed as a \u201ccore BST operation,\u201d but it is extremely important.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Traversal means visiting every node in a specific order.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For BSTs, the most important traversal is:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">In-Order Traversal<\/h3>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">Left \u2192 Node \u2192 Right<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This prints BST values in sorted order.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is one of the biggest reasons BSTs are useful.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Important Reality: BSTs Are Not Always Fast<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">BST performance depends on tree shape.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the tree stays balanced:<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">Search \u2248 O(log N)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the tree becomes skewed (like inserting sorted data):<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">Search \u2248 O(N)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Worst case BST becomes a linked list.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is exactly what our benchmarks later will demonstrate.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Actual Challenge: Implementation in C:<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">So like before, I will show you the header file and then we will discuss each function. This code is FULL of comments since the concepts here are sometimes a bit rough to grasp (or at least I found them hard to grasp upon first look): <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>bst.h<\/strong><\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">#ifndef BST_H\n#define BST_H\n\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n\nstruct Node {\n\n        int key;\n        struct Node* l;\n        struct Node* r;\n};\n\nstruct BST {\n        struct Node* root;\n};\n\nstruct Node* alloc_node(int key);\n\nint insert(int key, struct BST* tree);\n\nstruct Node* search(int key, struct BST* tree);\n\nstruct Node** search_for_delete(int key, struct BST* tree);\n\nint delete(int key, struct BST* tree);\n\nvoid free_subtree(struct Node* node);\n\nvoid destroy_bst(struct BST* tree);\n\nvoid print_sub_tree_in_order(struct Node* node);\n\nvoid print_tree_in_order(struct BST* tree);\n\n#endif<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>bst.c <\/strong><\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">#include &quot;bst.h&quot;\n\nstruct Node* alloc_node(int key) {\n    struct Node* new_node = malloc(sizeof(struct Node));\n    if (!new_node) return NULL; \/\/malloc failed, return NULL\n    new_node-&gt;key = key;\n    new_node-&gt;l = NULL;\n    new_node-&gt;r = NULL;\n    return new_node;\n}\n\nint insert(int key, struct BST* tree) {\n\n    if (!tree) return 1; \/\/error, bst pointer is invalid!\n\n    \/\/first, we handle the case where we are inserting into an empty tree\n    if (!tree-&gt;root) {\n        struct Node* new_root = alloc_node(key);\n        tree-&gt;root = new_root;\n        return 0;\n    }\n\n    \/\/okay so now, if the tree is not empty, we have to traverse the tree\n    \/\/to start, we grab the current root node\n    struct Node* curr_node = tree-&gt;root;\n\n    while (1) {\n        if (key &lt; curr_node-&gt;key) {\n            if (curr_node-&gt;l == NULL) {\n                struct Node* new_node = alloc_node(key);\n                if (!new_node) return 1; \/\/node allocation failed\n                curr_node-&gt;l = new_node;\n                return 0;\n            }\n            curr_node = curr_node-&gt;l; \/\/if the node is populated, we keep traversing\n        }\n        else if (key &gt; curr_node-&gt;key) {\n            if (curr_node-&gt;r == NULL) {\n                struct Node* new_node = alloc_node(key);\n                if (!new_node) return 1; \/\/node allocation failed\n                curr_node-&gt;r = new_node;\n                return 0;\n            }\n            curr_node = curr_node-&gt;r; \/\/if the node is populated, we keep traversing\n        }\n        else {\n            \/\/the key is equal... we will exlcude duplicate\n            return 2; \/\/duplicate error code!\n        }\n    }\n\n    return 0;\n}\n\nstruct Node* search(int key, struct BST* tree) {\n\n    if (!tree || !tree-&gt;root) return NULL; \/\/invalid tree pointer or tree is empty\n\n    struct Node* curr_node = tree-&gt;root;\n\n    while (1) {\n        if (key &lt; curr_node-&gt;key) { \/\/if less, we need to search down the left node\n            if (curr_node-&gt;l == NULL) return NULL; \/\/this means there is no left node, which means node is not in the tree, return NULL\n            curr_node = curr_node-&gt;l;\n        }\n        else if (key &gt; curr_node-&gt;key) { \/\/if more, we need to search down the right node\n            if (curr_node-&gt;r == NULL) return NULL; \/\/same as above, just for the right node\n            curr_node = curr_node-&gt;r;\n        }\n        else {\n            return curr_node;\n        }\n    }\n\n    return NULL; \/\/node not found, return NULL\n}\n\nstruct Node** search_for_delete(int key, struct BST* tree) {\n\n    if (!tree || !tree-&gt;root) return NULL;\n\n    struct Node** curr_node = &amp;tree-&gt;root;\n\n    while (1) {\n        if (key &lt; (*curr_node)-&gt;key) {\n            if ((*curr_node)-&gt;l == NULL) return NULL;\n            curr_node = &amp;(*curr_node)-&gt;l;\n        }\n        else if (key &gt; (*curr_node)-&gt;key) {\n            if ((*curr_node)-&gt;r == NULL) return NULL;\n            curr_node = &amp;(*curr_node)-&gt;r;\n        }\n        else {\n            return curr_node;\n        }\n    }\n\n    return NULL;\n}\n\nint delete(int key, struct BST* tree) {\n\n    struct Node** node_link = search_for_delete(key, tree);\n    if (!node_link) return 1; \/\/node not found, return 1\n\n    struct Node* node = *node_link;\n\n    \/\/this node is leaf node, deletion is easy\n    if (!node-&gt;l &amp;&amp; !node-&gt;r) {\n        *node_link = NULL;\n        free(node);\n        return 0;\n    }\n    else if (node-&gt;l &amp;&amp; !node-&gt;r) { \/\/only left node\n        *node_link = node-&gt;l;\n        free(node);\n        return 0;\n    }\n    else if (!node-&gt;l &amp;&amp; node-&gt;r) { \/\/only right node\n        *node_link = node-&gt;r;\n        free(node);\n        return 0;\n    }\n    else if (node-&gt;l &amp;&amp; node-&gt;r) {\n\n        \/\/if node has both children, it gets complicated, we need to preserve the correctness of the tree\n        \/\/to do this, we have to use a little trick\n        \/\/its called successor (another apporach is precessor)\n        \/\/we take the node to be deleted (which in this case, is *node_link or node as shown above)\n        \/\/then, we go down the RIGHT subtree of that node\n        \/\/once we go down the RIGHT subtree, we find the minimum node of that right subtree\n        \/\/since we know this subtree is valid (or we at least assume it is), we simply keep following the left nodes\n        \/\/once we hit the last left node, IE the mimimum, we stop\n        \/\/using that miminum value (the key of the node), we insert that value into the node we wish to delete\n        \/\/so now, instead of free(node), we ACTUALLY free the minimum node of the right subtree (the node we stole the key from)\n\n        \/\/so, grab the right node\n        struct Node** curr_node_link = &amp;node-&gt;r;\n\n        \/\/then, we iterate until we hit the minimum value by going deeper and deeper down the left path\n        while ((*curr_node_link)-&gt;l != NULL) {\n            curr_node_link = &amp;(*curr_node_link)-&gt;l;\n        }\n\n        \/\/the node we wish to delete is not deleted\n        \/\/instead we copy the value of minimum node into the node we wish to delete\n        struct Node* successor = *curr_node_link;\n        node-&gt;key = successor-&gt;key;\n\n        \/\/if our successor node has a right subtree, we need to make sure it stays connected to our BST\n        *curr_node_link = successor-&gt;r;\n\n        \/\/THEN, we actually delete the minimum node\n        free(successor);\n        return 0;\n    }\n\n    return 0;\n}\n\nvoid free_subtree(struct Node* node) {\n\n    if (node == NULL) return;\n\n    free_subtree(node-&gt;l);\n    free_subtree(node-&gt;r);\n    free(node);\n}\n\nvoid destroy_bst(struct BST* tree) {\n\n    if (tree == NULL) return;\n\n    free_subtree(tree-&gt;root);\n    tree-&gt;root = NULL;\n}\n\nvoid print_sub_tree_in_order(struct Node* node) {\n\n    if (node == NULL) return; \/\/we have reached the end, early exit\n\n    print_sub_tree_in_order(node-&gt;l);\n    printf(&quot;%d &quot;, node-&gt;key);\n    print_sub_tree_in_order(node-&gt;r);\n}\n\n\/\/and to close, we are going to write a function that traverses the tree in order and prints the keys\n\/\/doing this should just print a sorted list of numbers from low to high!\n\/\/that is what makes BSTs so useful, their definition forces useful properties\nvoid print_tree_in_order(struct BST* tree) {\n\n    \/\/to do this, we need print in this order:\n    \/\/left subtree -&gt; parent node -&gt; right subtree\n    \/\/this will be done recursively for this example\n    \/\/in the future we might use a stack or other structure to implement this functionality, havent made my mind up yet\n\n    \/\/therefore for this to work, we need to go all the way left (the smallest value), THEN PRINT\n\n    if (tree == NULL || tree-&gt;root == NULL) return; \/\/invalid tree pointer, early exit dont even try and print\n\n    print_sub_tree_in_order(tree-&gt;root);\n    printf(&quot;\\n&quot;);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And here is what we get when we benchmark our tree structure:<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">=== Binary Search Tree Benchmark ===\nReal implementation timing (includes malloc\/free)\n\n=============================\nBENCH N=1000\n=============================\n\n--- CASE: SORTED INSERT (worst-case shape) ---\ninsert       worst O(N)\/op        |      0.819 ms |      818.6 ns\/op\nsearch       worst O(N)\/op        |    914.651 ms |      457.3 ns\/op (REPS=2000000)\ndelete       worst O(N)\/op        |      0.008 ms |        8.1 ns\/op (OPS=1000)\n\n--- CASE: PSEUDO-RANDOM INSERT (average-ish shape) ---\ninsert       avg ~O(log N)\/op     |      0.055 ms |       54.8 ns\/op\nsearch       avg ~O(log N)\/op     |     89.296 ms |       44.6 ns\/op (REPS=2000000)\ndelete       avg ~O(log N)\/op     |      0.069 ms |       69.0 ns\/op (OPS=1000)\n\n=============================\nBENCH N=3000\n=============================\n\n--- CASE: SORTED INSERT (worst-case shape) ---\ninsert       worst O(N)\/op        |      5.284 ms |     1761.4 ns\/op\nsearch       worst O(N)\/op        |   3737.766 ms |     1868.9 ns\/op (REPS=2000000)\ndelete       worst O(N)\/op        |      0.021 ms |        7.0 ns\/op (OPS=3000)\n\n--- CASE: PSEUDO-RANDOM INSERT (average-ish shape) ---\ninsert       avg ~O(log N)\/op     |      0.157 ms |       52.4 ns\/op\nsearch       avg ~O(log N)\/op     |    121.852 ms |       60.9 ns\/op (REPS=2000000)\ndelete       avg ~O(log N)\/op     |      0.237 ms |       78.9 ns\/op (OPS=3000)\n\n=============================\nBENCH N=10000\n=============================\n\n--- CASE: SORTED INSERT (worst-case shape) ---\ninsert       worst O(N)\/op        |     99.876 ms |     9987.6 ns\/op\nsearch       worst O(N)\/op        |  16909.552 ms |     8454.8 ns\/op (REPS=2000000)\ndelete       worst O(N)\/op        |      0.088 ms |        8.8 ns\/op (OPS=10000)\n\n--- CASE: PSEUDO-RANDOM INSERT (average-ish shape) ---\ninsert       avg ~O(log N)\/op     |      0.661 ms |       66.1 ns\/op\nsearch       avg ~O(log N)\/op     |    152.629 ms |       76.3 ns\/op (REPS=2000000)\ndelete       avg ~O(log N)\/op     |      0.977 ms |       97.7 ns\/op (OPS=10000)\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Some Important Notes on the Benchmark<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Throughout this entire blog post I have hinted at the idea of a balanced tree. This tree we implemented is not &#8220;balanced&#8221; by any means depending on how you insert elements. If we were to insert elements that are already in order, we literally would have a BST that looks like a linked list at a 45 degree angle:<\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">Insert: 1, 2, 3, 4, 5\n\n1\n \\\n  2\n   \\\n    3\n     \\\n      4\n       \\\n        5<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pretty funny if you ask me, and the result of this is that our complexity table for operations ends up being identical to a linked list. The REAL power comes when we insert the values in a random order: <\/p>\n\n\n\n<pre data-line=\"\" class=\"wp-block-prismatic-blocks\"><code class=\"language-\">Insert Order: 3, 1, 5, 2, 4\n        3\n       \/ \\\n      1    5\n      \\   \/\n       2  4<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">I apologize for the really bad formatting on that above tree, but it should get the point across that the operations on this tree are faster than our unbalanced tree from before. It results in O(log(n)) complexity at best, and at worst, O(n) complexity since it essentially is a linked list. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So&#8230; what if there was a way to insert into a tree to force it to be balanced? There is (AVL tree), and that is going to be our next post which will be delayed since the Mardi Gras season is now upon us. For those of you unaware, Mardi Gras is Louisiana&#8217;s excuse to drop everything we are doing and party for a week straight \ud83d\ude42  <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is the portion of data structures and algorithms that I got completely lost on as a third year ECE major. It was also THE FIRST thing they taught us in that class (why they wouldn&#8217;t start with linear data structures like arrays and lists is beyond me). Fortunately there are TONS of resources to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-89","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/mattbick.dev\/index.php?rest_route=\/wp\/v2\/posts\/89","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mattbick.dev\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mattbick.dev\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mattbick.dev\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mattbick.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=89"}],"version-history":[{"count":1,"href":"https:\/\/mattbick.dev\/index.php?rest_route=\/wp\/v2\/posts\/89\/revisions"}],"predecessor-version":[{"id":90,"href":"https:\/\/mattbick.dev\/index.php?rest_route=\/wp\/v2\/posts\/89\/revisions\/90"}],"wp:attachment":[{"href":"https:\/\/mattbick.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=89"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mattbick.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=89"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mattbick.dev\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=89"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}