Interview Prep Hub

WordPress Development

WordPress powers 43% of the web. Interviews for WordPress developer roles go beyond themes and plugins — they test your understanding of the architecture, hooks system, the REST API, security hardening, performance optimization, and Gutenberg block development.

WordPress Architecture

WordPress is a monolithic PHP application built on top of MySQL/MariaDB. Understanding its request lifecycle is fundamental.

The Request Lifecycle

  1. wp-config.php: Loads database credentials, auth salts, and constants like WP_DEBUG.
  2. wp-settings.php: Bootstraps WordPress — loads core files, active plugins, and the active theme.
  3. Plugin Loading: All active plugins are loaded (their main PHP files are require-d). This is when plugin hooks fire.
  4. Theme Loading: The active theme's functions.php runs (acts like a plugin for the theme).
  5. Query Parsing: WordPress parses the URL using Rewrite Rules, determines the query (post, page, archive, etc.).
  6. Template Loading: The Template Hierarchy determines which template file renders the page.

The Template Hierarchy

WordPress uses a priority system to decide which template file to use. Interviewers love asking this.

// For a single blog post (post type "post"):
// 1. single-post-{slug}.php   (most specific)
// 2. single-post.php
// 3. single.php
// 4. singular.php
// 5. index.php                (ultimate fallback)

// For a custom post type "product":
// 1. single-product.php
// 2. single.php
// 3. singular.php
// 4. index.php

// For a category archive:
// 1. category-{slug}.php
// 2. category-{id}.php
// 3. category.php
// 4. archive.php
// 5. index.php

The Hook System — WordPress's Core Pattern

This is the most important concept in WordPress development. The entire plugin/theme ecosystem is built on hooks. It's essentially the Observer pattern.

Actions vs Filters

// ACTIONS — "Do something at this point in execution"
// They don't return anything; they execute side effects.

// Register a callback on the 'init' action hook
add_action('init', 'my_custom_post_types');

function my_custom_post_types() {
    register_post_type('product', [
        'label'  => 'Products',
        'public' => true,
        'supports' => ['title', 'editor', 'thumbnail'],
        'has_archive' => true,
    ]);
}

// Hook into 'wp_enqueue_scripts' to load CSS/JS
add_action('wp_enqueue_scripts', function() {
    wp_enqueue_style('main-css', get_stylesheet_uri(), [], '1.0.0');
    wp_enqueue_script('main-js', get_template_directory_uri() . '/js/app.js',
        ['jquery'], '1.0.0', true); // true = load in footer
});

// Common actions to know:
// init             — WordPress finished loading, before headers
// wp_enqueue_scripts — Enqueue frontend CSS/JS
// admin_menu       — Register admin menu pages
// save_post        — A post was saved (for custom processing)
// rest_api_init    — Register custom REST API endpoints
// wp_head          — Inject into <head>
// wp_footer        — Inject before </body>
// FILTERS — "Modify a value and return it"
// They MUST return the modified (or original) value.

// Modify the page title
add_filter('the_title', function($title) {
    if (is_single()) {
        return '📄 ' . $title;
    }
    return $title;
});

// Modify the main query
add_filter('pre_get_posts', function($query) {
    if (!is_admin() && $query->is_main_query() && is_home()) {
        $query->set('posts_per_page', 12);
        $query->set('post_type', ['post', 'product']);
    }
    return $query;
});

// Common filters to know:
// the_content      — Modify post content before display
// the_title        — Modify post title
// excerpt_length   — Change excerpt word count
// upload_mimes     — Allow/disallow file upload types
// body_class       — Add CSS classes to <body>
// wp_nav_menu_items — Modify navigation menu items

Hook Priority & Execution Order

// Third parameter is priority (default 10). Lower = runs first.
add_action('init', 'runs_second', 10);
add_action('init', 'runs_first', 5);
add_action('init', 'runs_third', 20);

// Fourth parameter is number of accepted arguments
add_filter('the_content', 'add_author_box', 10, 1);
add_action('save_post', 'sync_to_crm', 10, 3); // receives $post_id, $post, $update

The Loop & WP_Query

The Loop is how WordPress displays content. WP_Query is the class that powers it.

// The standard Loop (in template files)
if (have_posts()) :
    while (have_posts()) : the_post();
        the_title('<h2>', '</h2>');
        the_content();
        the_post_thumbnail('large');
    endwhile;
    the_posts_pagination();
else :
    echo '<p>No posts found.</p>';
endif;

// Custom WP_Query (for custom sections on a page)
$featured = new WP_Query([
    'post_type'      => 'product',
    'posts_per_page' => 6,
    'meta_key'       => 'is_featured',
    'meta_value'     => '1',
    'orderby'        => 'date',
    'order'          => 'DESC',
    'tax_query'      => [
        [
            'taxonomy' => 'product_category',
            'field'    => 'slug',
            'terms'    => 'electronics',
        ],
    ],
]);

if ($featured->have_posts()) :
    while ($featured->have_posts()) : $featured->the_post();
        // Display each product
    endwhile;
    wp_reset_postdata(); // CRITICAL: always reset after custom query
endif;

// get_posts() — simpler alternative for quick queries
$recent = get_posts([
    'post_type'   => 'post',
    'numberposts' => 5,
    'orderby'     => 'date',
]);

Plugin Development

<?php
/**
 * Plugin Name: My Custom Plugin
 * Description: A brief description of what it does.
 * Version: 1.0.0
 * Author: Arvind
 * Text Domain: my-custom-plugin
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

// Activation hook — runs once when plugin is activated
register_activation_hook(__FILE__, function() {
    // Create custom database tables
    global $wpdb;
    $table = $wpdb->prefix . 'custom_logs';
    $charset = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE IF NOT EXISTS $table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        action VARCHAR(100) NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_user (user_id)
    ) $charset;";
    
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);
    
    // Flush rewrite rules if registering custom post types
    flush_rewrite_rules();
});

// Deactivation hook
register_deactivation_hook(__FILE__, function() {
    flush_rewrite_rules();
});

// Use OOP structure for larger plugins
class MyCustomPlugin {
    private static ?self $instance = null;
    
    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    private function __construct() {
        add_action('init', [$this, 'registerPostTypes']);
        add_action('rest_api_init', [$this, 'registerApiRoutes']);
        add_action('admin_menu', [$this, 'registerAdminPages']);
    }
    
    public function registerPostTypes(): void { /* ... */ }
    public function registerApiRoutes(): void { /* ... */ }
    public function registerAdminPages(): void { /* ... */ }
}

MyCustomPlugin::getInstance();

WordPress REST API

The REST API (since 4.7) makes WordPress a headless CMS. Built-in endpoints at /wp-json/wp/v2/.

// Register a custom REST endpoint
add_action('rest_api_init', function() {
    register_rest_route('myplugin/v1', '/products', [
        'methods'             => 'GET',
        'callback'            => 'get_products',
        'permission_callback' => '__return_true', // Public endpoint
        'args' => [
            'category' => [
                'required'          => false,
                'validate_callback' => fn($param) => is_string($param),
                'sanitize_callback' => 'sanitize_text_field',
            ],
        ],
    ]);
    
    register_rest_route('myplugin/v1', '/products', [
        'methods'             => 'POST',
        'callback'            => 'create_product',
        'permission_callback' => function() {
            return current_user_can('edit_posts'); // Auth check
        },
    ]);
});

function get_products(WP_REST_Request $request): WP_REST_Response {
    $category = $request->get_param('category');
    
    $query = new WP_Query([
        'post_type' => 'product',
        'posts_per_page' => 20,
    ]);
    
    $products = array_map(function($post) {
        return [
            'id'    => $post->ID,
            'title' => $post->post_title,
            'price' => get_post_meta($post->ID, 'price', true),
        ];
    }, $query->posts);
    
    return new WP_REST_Response($products, 200);
}

// Built-in endpoints you should know:
// GET    /wp-json/wp/v2/posts          — List posts
// GET    /wp-json/wp/v2/posts/{id}     — Single post
// POST   /wp-json/wp/v2/posts          — Create post (auth required)
// PUT    /wp-json/wp/v2/posts/{id}     — Update post
// DELETE /wp-json/wp/v2/posts/{id}     — Delete post
// GET    /wp-json/wp/v2/users/me       — Current user info

Gutenberg & Block Development

Gutenberg is WordPress's block-based editor, built with React. Block development is now a key WordPress skill.

// block.json — Block metadata (required since WP 5.8)
{
    "apiVersion": 3,
    "name": "myplugin/hero-banner",
    "title": "Hero Banner",
    "category": "design",
    "icon": "cover-image",
    "description": "A customizable hero banner block.",
    "supports": {
        "html": false,
        "align": ["wide", "full"],
        "color": { "background": true, "text": true }
    },
    "attributes": {
        "heading": { "type": "string", "default": "" },
        "imageUrl": { "type": "string", "default": "" }
    },
    "editorScript": "file:./index.js",
    "editorStyle": "file:./index.css",
    "style": "file:./style-index.css"
}

// Register in PHP
add_action('init', function() {
    register_block_type(__DIR__ . '/blocks/hero-banner');
});

// edit.js (React component for the editor)
import { useBlockProps, RichText, MediaUpload } from '@wordpress/block-editor';

export default function Edit({ attributes, setAttributes }) {
    const blockProps = useBlockProps();
    return (
        <div {...blockProps}>
            <RichText
                tagName="h1"
                value={attributes.heading}
                onChange={(heading) => setAttributes({ heading })}
                placeholder="Enter heading..."
            />
        </div>
    );
}

Security Hardening

WordPress security is a favourite interview topic because of the platform's massive attack surface.

  • Data Sanitization: Use sanitize_text_field(), sanitize_email(), wp_kses_post() on all user inputs.
  • Data Escaping: Use esc_html(), esc_attr(), esc_url(), wp_kses() on all outputs.
  • Nonce Verification: All form submissions and AJAX calls must include a nonce (wp_nonce_field(), wp_verify_nonce()).
  • Capability Checks: Always check current_user_can('manage_options') before privileged operations.
  • Database Queries: Use $wpdb->prepare() for all custom queries. Never interpolate user input.
  • File Permissions: wp-config.php should be 400/440. Disable file editing in the admin: define('DISALLOW_FILE_EDIT', true).

Performance Optimization

  • Object Caching: Use Redis or Memcached as a persistent object cache (via wp_cache_set/get). Without this, WordPress queries the DB on every page load.
  • Transients API: Cache expensive queries: set_transient('key', $data, HOUR_IN_SECONDS). Stored in the database (or object cache if available).
  • Full-Page Caching: Plugins like WP Super Cache or server-level (Nginx FastCGI cache, Varnish) bypass PHP entirely for cached pages.
  • Autoloaded Options: Options with autoload=yes are loaded into memory on every page load. Audit large autoloaded options (serialized arrays in wp_options).
  • Reduce Plugins: Each plugin fires hooks and loads files. 50+ plugins = slow site. Audit with Query Monitor plugin.
  • Image Optimization: Use WebP/AVIF formats, lazy loading (loading="lazy" is default since WP 5.5), and proper srcset for responsive images.

Headless WordPress

Using WordPress as a backend CMS with a decoupled frontend (Next.js, Nuxt, Gatsby).

  • Architecture: WordPress serves content via REST API or WPGraphQL plugin. A separate frontend app fetches and renders it.
  • Pros: Modern frontend stack, better performance (SSG/ISR), improved security (no PHP frontend exposed), flexible multi-platform delivery.
  • Cons: No live preview (without extra work), no visual editor for non-technical users, plugin frontend features break, increased complexity.
  • WPGraphQL: A popular plugin that exposes WordPress data via a GraphQL API, more efficient than REST for complex frontends.

WordPress Multisite

A single WordPress installation managing multiple sites. Common in enterprises and SaaS platforms.

  • Use Cases: University departments, franchise websites, white-label SaaS products.
  • Subdomain vs Subdirectory: site1.example.com vs example.com/site1.
  • Super Admin: Has access to all sites and the Network Admin dashboard.
  • Shared Resources: Plugins and themes are installed once and activated per-site.

Interview Quick Reference

TopicKey Points to Mention
HooksActions (side effects) vs Filters (modify values). Priority system. add_action/add_filter.
Template HierarchyMost specific to least specific. single-{post_type}.php → single.php → index.php.
SecuritySanitize inputs, escape outputs, nonces for forms, $wpdb→prepare for queries, capability checks.
REST APIregister_rest_route, permission_callback, WP_REST_Response. Built-in at /wp-json/wp/v2/.
PerformanceObject cache (Redis), transients, full-page cache, autoload audit, Query Monitor for profiling.
GutenbergBlock-based editor built with React. block.json, @wordpress/block-editor, Server-side rendering.