Land and Buildings Transaction Tax#

Land and Buildings Transaction Tax (LBTT) is a property transaction tax administrated by Revenue Scotland in Scotland. It replaced the UK’s Stamp Duty Land Tax (SDLT) for property transactions in Scotland starting from April 1, 2015. Similar to SDLT, LTT is imposed on property or land transactions, including purchases, transfers, and leases, with values over a certain threshold. It applies to both residential and non-residential (commercial) properties.

Land and Buildings Transaction Tax parameters can be found in policyengine_uk/parameters/gov/revenue_scotland/lbtt and logic in policyengine_uk/variables/gov/revenue_scotland/lbtt.py.

Legislation#

The regulation of LBTT is defined in 2013 by Land and Buildings Transaction Tax (Scotland) Act 2013. In 2015, the Scottish Government issued The Land and Buildings Transaction Tax (Tax Rates and Tax Bands) (Scotland) Order 2015, which specifies additional regulations, such as tax bands, rates, and reliefs, and also defines the effective date of the LBTT.

Rates#

Similar to SDLT, the rates of LBTT are structured with different threshold and tax bands based on the price of the property. The LBTT rates vary for each band, with higher rates applied to properties with prices that exceed a particular threshold. The rates and thresholds differ for purchase of residential and non-residential properties and rent. There is an additiional charge called Additional Dwelling Supplement (ADS) imposed on the purchase of additional residential properties, resulting in a higher rate of tax. First-time residential property buyers are eligible to waive LBTT if the property price no more than £175,000 (a relief up to £600).

The rules and thresholds for LBTT may change over time due to government policy adjustments. Here we show current rates and thresholds for LBTT in 2023 below:

  • Residential

    • First-time

      Up to £175,000: 0%

      £175,000 to £250,000: 2%

      £250,000 to £325,000: 5%

      £325,000 to £750,000: 10%

      Over £750,000: 12%

    • Primary (non first-time)

      Up to £145,000: 0%

      £145,000 to £250,000: 2%

      £250,000 to £325,000: 5%

      £325,000 to £750,000: 10%

      Over £750,000: 12%

    • Non-primary (ADS): An additional 6% on top of the rates for primary properties

      Up to £145,000: 6%

      £145,000 to £250,000: 8%

      £250,000 to £325,000: 11%

      £325,000 to £750,000: 16%

      Over £750,000: 18%

  • Non-residential purchase

    Up to £150,000: 0%

    £150,000 to £250,000: 1%

    Over £250,000: 5%

  • Non-residential rent

    Up to £150,000: 0%

    £150,000 to £2,000,000: 1%

    Over £2,000,000: 5%

Hide code cell source
import pandas as pd
import plotly.express as px
from policyengine_core.charts import format_fig
from policyengine_uk import CountryTaxBenefitSystem

system = CountryTaxBenefitSystem()
lbtt = system.parameters.gov.revenue_scotland.lbtt

parameters = [
    lbtt.residential.rate,
    lbtt.residential.first_time_buyer_rate,
    lbtt.non_residential,
    lbtt.rent
]
thresholds = []
rates = []
labels = []

for parameter in parameters:
    for param in parameter.brackets:
        thresholds.append(param.threshold.values_list[0].value)
        rates.append(param.rate.values_list[0].value)
        labels.append(parameter.metadata.get("label"))

# add a row showing tax rate when property price is higher than the largest threshold for each property type
price = max(thresholds)+200000
for parameter in parameters:
    thresholds.append(price)
    rates.append(parameter.brackets[-1].rate.values_list[0].value)
    labels.append(parameter.metadata.get("label"))

df = pd.DataFrame({
    "Threshold": thresholds,
    "Rate": rates,
    "Label": labels
})

# add rates for secondary residences
ars = lbtt.residential.additional_residence_surcharge.values_list[0].value
df_secondary = df[df.Label=="LBTT on residential property"]
df_secondary.loc[:,"Label"]="LBTT on secondary residences"
df_secondary["Rate"] = df_secondary["Rate"].apply(lambda x: x+ars)
df = pd.concat([df, df_secondary])

# plot
fig = px.line(
    df,
    x="Threshold",
    y="Rate",
    color="Label",
    title="Land and Buildings Transaction Tax (LBTT) rates over property price thresholds"
).update_layout(
    yaxis_tickformat=",.0%",
    xaxis_tickprefix="£",
    legend_title=""
).update_traces(
    line_shape="hv"
)
fig = format_fig(fig)
fig

The first-time buyer relief only applies for homes priced under £175,000. Here we simulate the marginal effect of LBTT.

Hide code cell source
from policyengine_uk import Simulation

sim = Simulation(
    situation=dict(
        households=dict(
            household=dict(
                main_residential_property_purchased_is_first_home=True,
                region="SCOTLAND",
                members=["person"]
            )
        ),
        people=dict(
            person=dict(
                age=30,
            )
        ),
        axes=[[
            dict(
                name="main_residential_property_purchased",
                min=0,
                max=5_000_000,
                count=1_000,
            )
        ]]
    )
)

import pandas as pd

lbtt_sim = sim.calculate("land_and_buildings_transaction_tax")
home_price = sim.calculate("main_residential_property_purchased")

marginal_rate = (lbtt_sim[1:] - lbtt_sim[:-1]) / (home_price[1:] - home_price[:-1])
home_price = home_price[:-1]

df = pd.DataFrame({
    "Home price": home_price,
    "Marginal LBTT rate": marginal_rate,
    "LBTT": lbtt_sim[:-1]
})

import plotly.express as px
from plotly.subplots import make_subplots

# left shows marginal rate, right shows total LBTT

fig = make_subplots(rows=1, cols=2, subplot_titles=("Marginal LBTT rate", "Total LBTT"))

fig.add_trace(
    px.line(
        df,
        x="Home price",
        y="Marginal LBTT rate",
        title = "Marginal LBTT rate"
    ).update_traces(
        line_shape="hv"
    ).data[0],
    row=1, col=1
).update_xaxes(
    row=1, col=1,
    title_text="Price",
    tickprefix="£"
).update_yaxes(
    row=1, col=1,
    tickformat=",.0%"
)

fig.add_trace(
    px.line(
        df,
        x="Home price",
        y="LBTT",
        title = "Total LBTT"
    ).update_traces(
        line_shape="hv"
    ).data[0],
    row=1, col=2
).update_xaxes(
    row=1, col=2,
    title_text="Price",
    tickprefix="£"
).update_yaxes(
    row=1, col=2,
    tickprefix="£"
)

fig = format_fig(fig)
fig